2012-05-17 3 views
5

Biorąc pod uwagę następujący program:Korzystanie dynamiczny ustawić odmienne właściwości niekontrolowany (osoby trzeciej) uszczelniony rodzajów

using System; 
using System.Collections.Generic; 

namespace ConsoleApplication49 
{ 
    using FooSpace; 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      IEnumerable<FooBase> foos = FooFactory.CreateFoos(); 

      foreach (var foo in foos) 
      {    
       HandleFoo(foo); 
      } 
     } 

     private static void HandleFoo(FooBase foo) 
     { 
      dynamic fooObject = foo; 
      ApplyFooDefaults(fooObject); 
     } 

     private static void ApplyFooDefaults(Foo1 foo1) 
     { 
      foo1.Name = "Foo 1"; 

      Console.WriteLine(foo1); 
     } 

     private static void ApplyFooDefaults(Foo2 foo2) 
     { 
      foo2.Name  = "Foo 2"; 
      foo2.Description = "SomeDefaultDescription"; 

      Console.WriteLine(foo2); 
     } 

     private static void ApplyFooDefaults(Foo3 foo3) 
     { 
      foo3.Name = "Foo 3"; 
      foo3.MaxSize = Int32.MaxValue; 

      Console.WriteLine(foo3); 
     } 

     private static void ApplyFooDefaults(Foo4 foo4) 
     { 
      foo4.Name  = "Foo 4"; 
      foo4.MaxSize  = 99999999; 
      foo4.EnableCache = true; 

      Console.WriteLine(foo4); 
     } 

     private static void ApplyFooDefaults(FooBase unhandledFoo) 
     { 
      unhandledFoo.Name = "Unhandled Foo"; 
      Console.WriteLine(unhandledFoo); 
     } 
    }  
} 

///////////////////////////////////////////////////////// 
// Assume this namespace comes from a different assembly 
namespace FooSpace 
{ 
    //////////////////////////////////////////////// 
    // these cannot be changed, assume these are 
    // from the .Net framework or some 3rd party 
    // vendor outside of your ability to alter, in 
    // another assembly with the only way to create 
    // the objects is via the FooFactory and you 
    // don't know which foos are going to be created 
    // due to configuration. 

    public static class FooFactory 
    { 
     public static IEnumerable<FooBase> CreateFoos() 
     { 
      List<FooBase> foos = new List<FooBase>(); 
      foos.Add(new Foo1()); 
      foos.Add(new Foo2()); 
      foos.Add(new Foo3()); 
      foos.Add(new Foo4()); 
      foos.Add(new Foo5()); 

      return foos; 
     } 
    } 

    public class FooBase 
    { 
     protected FooBase() { } 

     public string Name { get; set; } 

     public override string ToString() 
     { 
      return String.Format("Type = {0}, Name=\"{1}\"", this.GetType().FullName, this.Name); 
     } 
    } 

    public sealed class Foo1 : FooBase 
    { 
     internal Foo1() { } 
    } 

    public sealed class Foo2 : FooBase 
    { 
     internal Foo2() { } 

     public string Description { get; set; } 

     public override string ToString() 
     { 
      string baseString = base.ToString(); 
      return String.Format("{0}, Description=\"{1}\"", baseString, this.Description); 
     } 
    } 

    public sealed class Foo3 : FooBase 
    { 
     internal Foo3() { } 

     public int MaxSize { get; set; } 

     public override string ToString() 
     { 
      string baseString = base.ToString(); 
      return String.Format("{0}, MaxSize={1}", baseString, this.MaxSize); 
     } 
    } 

    public sealed class Foo4 : FooBase 
    { 
     internal Foo4() { } 

     public int MaxSize { get; set; } 
     public bool EnableCache { get; set; } 

     public override string ToString() 
     { 
      string baseString = base.ToString(); 
      return String.Format("{0}, MaxSize={1}, EnableCache={2}", baseString, 
                     this.MaxSize, 
                     this.EnableCache); 
     } 
    } 

    public sealed class Foo5 : FooBase 
    { 
     internal Foo5() { } 
    } 
    //////////////////////////////////////////////// 
} 

która produkuje następujące dane wyjściowe:

Type = ConsoleApplication49.Foo1, Name="Foo 1" 
Type = ConsoleApplication49.Foo2, Name="Foo 2", Description="SomeDefaultDescription" 
Type = ConsoleApplication49.Foo3, Name="Foo 3", MaxSize=2147483647 
Type = ConsoleApplication49.Foo4, Name="Foo 4", MaxSize=99999999, EnableCache=True 
Type = ConsoleApplication49.Foo5, Name="Unhandled Foo" 
Press any key to continue . . . 

Zdecydowałem się użyć dynamicznej tutaj, aby uniknąć następujące:

  1. za pomocą instrukcji switch/if/else np. switch(foo.GetType().Name)
  2. jawne instrukcje sprawdzania typu, np. foo is Foo1
  3. wyraźne instrukcje rzutowania np. (Foo1)foo

powodu zamiany dynamic, prawidłowa metoda ApplyFooDefaults dostaje wywoływany w zależności od typu obiektu przekazany do HandleFoo(FooBase foo). Wszelkie obiekty, które nie mają odpowiedniej metody obsługi, należą do metody "catch all", ApplyFooDefaults(FooBase unhandledFoo).

Kluczowym elementem jest to, że FooBase i klas pochodnych reprezentują typy, które są poza naszą kontrolą i nie mogą być uzyskane z dodanie dodatkowych interfejsów.

Czy jest to "dobre" użycie dynamiczne, czy problem ten można rozwiązać w sposób OOP, nie dodając dodatkowej złożoności, biorąc pod uwagę ograniczenia i fakt, że jest to po prostu ustawienie domyślnych wartości właściwości tych obiektów?

* AKTUALIZACJA *

Po odpowiedź Boba Horna, zdałem sobie sprawę, że mój scenariusz nie był kompletny. Dodatkowe ograniczenia:

  1. Nie możesz bezpośrednio tworzyć Foos, musisz użyć FooFactory.
  2. Nie można założyć typu Foo, ponieważ typ Foo jest określony w konfiguracji i jest tworzony w sposób refleksyjny.

.

+1

@Jon Skeet - słyszałem, że mówisz w Code Mash 2012 w Sandusky, OH. W większości przypadków wydawało się, że nie zależy ci na użyciu typów dynamicznych w żadnej sytuacji. Chciałbym poznać twoje zdanie na ten temat. –

+1

Myślę, że twój kod jest bardzo, * bardzo * dobry, jak jest. – Alex

+1

Myślę, że warto rozważyć użycie generycznych, jeśli używasz dynamicznego. Podobne: http://stackoverflow.com/q/10132760/1026459. Uwaga: Jon Skeet zapewnia odpowiedź w tym linku. –

Odpowiedz

1

Dobrze, indywidualne inicjalizacji obiektu powinno się zdarzyć w konstruktorów typów. Jeśli fabryka nie zrobi tego i wyprowadza obiekt tylko z typem podstawowym, to oczywiście jest poza wzorami OOP, aby zainicjować obiekty na podstawie typu.

Niestety, wykrywanie typu Runtime jest droga i dynamiczny właśnie robi, więc tak, rozwiązanie jest bardzo ładna.(ale lib strony trzeciej nie jest, ponieważ zmusza cię do używania typów dynamicznych)

+0

@M. Stramm, myślę, że jest to problem związany z refleksyjnie tworzonymi typami w oparciu o konfigurację. Gdzie kod nie zna konkretnego typu, który zostanie utworzony do czasu wykonania. Teraz, jeśli posiadasz kod, na pewno możesz ustawić swoje domyślne ustawienia w konstruktorze. Ale jeśli chodzi o klasy od dostawców zewnętrznych, o ile nie dają konkretnych haczyków, wydaje się, że jest to jedyna metoda dynamicznej inspekcji tego typu, wykorzystująca dynamikę lub w inny sposób. – Jim

+0

@ Jim Ustalono. Zasadniczo to fabryka jest odpowiedzialna za inicjowanie obiektów, a kod wywołujący nigdy nie powinien martwić się o typ tworzonych obiektów. Jeśli fabryka nie zrobi tego, jesteś * zmuszony * do złamania wzorca i używania sprawdzania typu w czasie wykonywania. Więc starałem się powiedzieć, że nie ma lepszej metody, ponieważ fabryka stron trzecich nie gra uczciwie. –

+0

Mam zamiar przyjąć twoją odpowiedź pod nieobecność innych osób, które używają alternatywnego podejścia do OOP. – Jim

0

Możliwym sposobem, aby to zrobić i uniknąć dynamikę byłoby dokonać ApplyFooDefaults() metody rozszerzenie:

public static class FooExtensions 
{ 
    public static void ApplyFooDefaults(this Foo1 foo1) 
    { 
     foo1.Name = "Foo 1"; 

     Console.WriteLine(foo1); 
    } 

    public static void ApplyFooDefaults(this Foo2 foo2) 
    { 
     foo2.Name = "Foo 2"; 
     foo2.Description = "SomeDefaultDescription"; 

     Console.WriteLine(foo2); 
    } 

    public static void ApplyFooDefaults(this Foo3 foo3) 
    { 
     foo3.Name = "Foo 3"; 
     foo3.MaxSize = Int32.MaxValue; 

     Console.WriteLine(foo3); 
    } 

    public static void ApplyFooDefaults(this Foo4 foo4) 
    { 
     foo4.Name = "Foo 4"; 
     foo4.MaxSize = 99999999; 
     foo4.EnableCache = true; 

     Console.WriteLine(foo4); 
    } 

    public static void ApplyFooDefaults(this FooBase unhandledFoo) 
    { 
     unhandledFoo.Name = "Unhandled Foo"; 
     Console.WriteLine(unhandledFoo); 
    } 
} 

W pewnym momencie w swoim programie, trzeba stworzyć każdy foo. Gdy to zrobisz, wywołać metody rozszerzenie następnie:

static void Main(string[] args) 
{ 
    List<FooBase> foos = new List<FooBase>(); 

    Foo1 foo1 = new Foo1(); 
    foo1.ApplyFooDefaults(); 
    foos.Add(foo1); 

    Foo2 foo2 = new Foo2(); 
    foo2.ApplyFooDefaults(); 
    foos.Add(foo2); 

    Foo3 foo3 = new Foo3(); 
    foo3.ApplyFooDefaults(); 
    foos.Add(foo3); 

    Foo4 foo4 = new Foo4(); 
    foo4.ApplyFooDefaults(); 
    foos.Add(foo4); 

    Foo5 foo5 = new Foo5(); 
    foo5.ApplyFooDefaults(); 
    foos.Add(foo5); 

    Console.WriteLine("Press any key to end."); 
    Console.ReadKey(); 
} 

enter image description here

+0

Zapomniałem jednego aspektu problemu, Foos są tworzone poprzez odbicie również poza naszą kontrolą (z konfiguracji). Zaktualizuję ten przykład, aby to odzwierciedlić. – Jim

+0

Ponadto, nie wiesz, które Foos są tworzone, ponieważ typ jest określony w konfiguracji, hipotetycznie. – Jim

+0

LOL. Zamierzasz dodawać ograniczenia, dopóki jedyna pozostawiona opcja nie będzie dynamiczna, prawda? :) –