2015-05-05 3 views
4

У нас есть приложение, которое в целом используется как «общий» продукт несколькими разными клиентами, и в целом те же функции доступны для всех из них. Однако есть некоторые индивидуально разработанные компоненты, особенно относящиеся к импорту данных из соответствующих внутренних систем клиентов.Расширение классов базовых моделей для конкретных случаев использования

До сих пор это в основном означало преобразование различных входных форматов в одни и те же структуры данных, но все чаще становится важным сохранять дополнительные данные для конкретных клиентов для некоторых типов и использовать их снова в отдельных частях кода, которые также «знают» о это конкретный клиент. В целом это не будет много разных мест в приложении.

Чтобы сделать это без «загрязняющих» классов основных моделей, эта «расширяемость» в настоящее время достигается за счет того, что классы, о которых идет речь, получают от DynamicObject, а затем имеют некоторый код для динамического определения свойств, связанных с магическими строками и в других местах проверьте их и извлеките их, что даже не безопасно для типов, так как вам нужно знать, для чего нужно сделать возможное значение.

Я довольно недоволен этим и хотел бы предложить решение, которое немного напоминает «переключение обезьян» и более идиоматично в статически типизированной среде. Вопрос в том, какой лучший вариант для этого. (Дополнительное требование состоит в том, что дополнительные данные должны быть легко сериализаций/десериализации с остальной частью объекта.)

  • «очевидная» вещь в объектно-ориентированном языке, конечно, будет подклассы типов моделей, но это создает ряд новых проблем при создании и копировании объектов и, как правило, мне не нравится то, что мне очень нравится (и это просто нарушает «состав над наследованием»).
  • Решение, которое по-прежнему использует наследование, но ограничивает его влияние, будет добавление единственного свойства ExtensionData к фактическому классу, его тип будет пустым классом *ExtensionData, который затем подклассифицируется для каждого клиента. Использование этого метода предполагает некоторую проверку и литье типов для фактического объекта данных расширения, но помимо этого, он будет полностью безопасным по типу. Я думаю, что это мой любимый способ прямо сейчас.
  • То, что очень похоже на используемый динамический подход, будет заменять типизированный объект из предыдущего варианта Dictionary<string, object>. Я думаю, что это все еще лучше, чем сейчас, но у него есть одна и та же фундаментальная проблема: динамически набирать и использовать магические строки (хотя они, конечно, будут храниться как константы).

Данные расширения не должны быть фактически динамическими; как только известно, какие данные клиента импортируются, мы находимся в четко определенном субдомене без каких-либо сюрпризов относительно того, что находится в импортируемых данных.

(Как это часто бывает, это было бы проще в F #, где я мог бы использовать свойства расширенного расширения или иметь ExtensionData как DU, где каждый случай имеет тип записи конкретного клиента, который будет однозначно идентифицирован с помощью сопоставления с образцом .)

Что является самым «идиоматическим» и поддерживаемым способом сделать это на C#? Есть ли у одной из упомянутых альтернатив огромные преимущества или недостатки, которые я не рассматривал?

ответ

0

Вы могли бы использовать что-то вроде

public class BaseClass 
{ 
    Dictionary<Type, object> propertys = new Dictionary<Type, object>(); 

    public void Add<T>(T instance) 
    { 
     propertys[typeof(T)] = instance; 
    } 

    public T Get<T>() 
    { 
     return (T)propertys[typeof(T)]; 
    } 

    public void Test() 
    { 
     Add<string>("Hello World"); 

     string helloWorld = Get<string>(); 
    } 
} 

Ограничение, что вы можете только добавить один экземпляр/свойства для данного типа.

Если производительность не является проблемой, другое решение может быть следующим. Но будьте осторожны с использованием выражений, подобных этому медленно (er). Вы даете ему лямбда, но неявное преобразование к Expression занимает некоторое время

using System.Linq.Expressions; 

// There will never be an implementation of this interface 
// Its only there to "define" the property names and types without 
// magic strings and in a type safe way 
public interface IBaseClassExtension 
{ 
    public string ExtensionProperty { get; set; } 
    public int ExtensionProperty2 { get; set; } 
} 

public class BaseClass 
{ 
    Dictionary<string, object> propertys = new Dictionary<string, object>(); 

    public void Add<T, U>(Expression<Func<T, U>> expr, U instance) 
    { 
     var propExpr = expr.Body as MemberExpression; 
     // The declaring types name could be used in addition to be sure there 
     // is no naming conflict with members of other types that have the same member name 
     //string name = propExpr.Member.DeclaringType.FullName + propExpr.Member.Name; 
     propertys[propExpr.Member.Name] = instance; 
    } 

    public U Get<T, U>(Expression<Func<T, U>> expr) 
    { 
     var propExpr = expr.Body as MemberExpression; 
     //string name = propExpr.Member.DeclaringType.FullName + propExpr.Member.Name; 
     return (U)propertys[propExpr.Member.Name]; 
    } 

    public void Test() 
    { 
     Add<IBaseClassExtension, string>(bce => bce.ExtensionProperty, "Hello World"); 
     string helloWorld = Get<IBaseClassExtension, string>(bce => bce.ExtensionProperty); 
    } 
} 

Оба решения могут быть использованы безопасным способом типа, даже словарь зная только объекты

Если у вас есть время, чтобы ждать для C# 6.0 новое имя оператора должно быть намного быстрее, чем использование выражений. Но вы потеряете некоторую безопасность.

public interface IBaseClassExtension 
{ 
    public string ExtensionProperty { get; set; } 
    public int ExtensionProperty2 { get; set; } 
} 

public class BaseClass 
{ 
    Dictionary<string, object> propertys = new Dictionary<string, object>(); 

    public void Add(string name, object instance) 
    { 
     propertys[name] = instance; 
    } 

    public T Get<T>(string name) 
    { 
     return (T)propertys[name]; 
    } 

    public void Test() 
    { 
     Add(nameof(IBaseClassExtension.ExtensionProperty), "HelloWorld"); 
     string helloWorld = Get<string>(nameof(IBaseClassExtension.ExtensionProperty)); 
    } 
} 

Edit:

Если количество расширений propertys/данные не так много другое решение могло бы сделать базовую модель общего

public class BaseClass 
{ 
} 

public class BaseClass<T, U, V> : BaseClass 
{ 
    public T Extension1 { get; set; } 
    public U Extension2 { get; set; } 
    public V Extension3 { get; set; } 
} 

public class ClassThatIsUsingSpecificBaseClass 
{ 
    public void Test(BaseClass<int, int, string> baseClass) 
    { 
     //baseClass.Extension1 ...; 
     //baseClass.Extension2 ...; 
    } 
} 

public class ClassThatIsUsingBaseClass 
{ 
    public void Test(BaseClass baseClass) 
    { 
    } 
} 

Это будет типизированным, не слепки не и был бы только один основанный BaseClass, а не один для каждого уникального требования/данных клиента.

0

Я не уверен, какой будет самый идиоматический путь, но я с вами, что я бы предпочел решение, которое использует агрегацию.

Если вы настроили его определенным образом, используя набор интерфейсов маркеров и помощников, предпочтительно только одну конкретную реализацию на каждый клиентский интерфейс, вы можете сделать это относительно безопасным по типу, а также предоставить некоторый общий доступ с инфраструктурой, которая позволяет отображать эти данные, например словарь.

Я создал очень грубую прототипную реализацию, чтобы проиллюстрировать некоторые идеи. Здесь я расскажу об основных моментах, «полный» пример можно найти в этом gist (около 200 строк).

Мы начинаем с двух интерфейсов маркера для расширяемого класса и класса данных расширения соответственно, которые легко обнаруживаются с помощью отражения.

// Marker interface, allowing reflection & discovery 
public interface IExtendable {} 

// Marker interface, allowing reflection & discovery 
public interface IExtensionData {} 

И мы делаем это более типа сохранить и закрепить отношения агрегации:

Далее мы определяем интерфейс, который должен быть реализован поставщиком специфических фрагментов данных расширения для конкретного клиент.

// Factory implemented for each specific customer, discoverable. 
public interface IExtensionProvider 
{ 
    IExtensionDataFor<TExtendable> CreateExtensionData<TExtendable>() 
     where TExtendable : class, IExtendableOf<TExtendable>; 

    IExtensionDataFor<TExtendable> CreateExtensionDataFor<TExtendable>(TExtendable instance) 
     where TExtendable : class, IExtendableOf<TExtendable>; 
} 

Чтобы сделать бетон (используя глупый пример), позволяет предположить, в вашем «общий» продукт, у вас есть класс PhoneNumber, как это:

// Example "core" class with extension data 
public class PhoneNumber : IExtendableOf<PhoneNumber> 
{ 
    public enum Kind { Internal, External } 
    public string AreaCode { get; set; } 
    public string Number { get; set; } 
    public Kind NumberKind { get; set; } 
    public virtual IExtensionDataFor<PhoneNumber> ExtensionData { get; set; } 
} 

И в ваших данных «CustomerOne» расширения сборки у вас есть расширение, определенное для этого класса, как:

// Customer specific extension data for a phone number 
public class PhoneNumberExtension : IExtensionDataFor<PhoneNumber> 
{ 
    public string Prefix { get; set; } 
} 

Затем, опуская сантехнику, которая присутствует в сущности, вы можете использовать его, например, например:

// A quick & dirty example. 
public static class TryExtensions 
{ 
    static TryExtensions() 
    { 
     ExtensionProvider.Use(new TryOuts.ExtensionData.CustomerOne.ExtensionProvider()); 
    } 

    public static void Run() 
    { 
     var phoneNumberOne = new PhoneNumber { NumberKind = PhoneNumber.Kind.Internal, AreaCode = "(231)", Number = "567 891 123" }.Extend(); 
     var phoneNumberTwo = new PhoneNumber { NumberKind = PhoneNumber.Kind.External, AreaCode = "(567)", Number = "555 666 777" }.Extend(); 
     Console.WriteLine("Phone Number 1: {0}", phoneNumberOne.ExtensionData.ToDictionary()["Prefix"]); 
     Console.WriteLine("Phone Number 2: {0}", phoneNumberTwo.ExtensionData.ToDictionary()["Prefix"]); 
     CodeThatKnowsCustomerOne(phoneNumberOne); 
    } 

    public static void CodeThatKnowsCustomerOne(PhoneNumber number) 
    { 
     var extensionData = number.ExtensionData as TryOuts.ExtensionData.CustomerOne.PhoneNumberExtension; 
     Console.WriteLine("Prefix: {0}", extensionData.Prefix); 
    } 
} 
Смежные вопросы