扩展特定用例的基本模型类

时间:2015-05-05 02:31:50

标签: c# oop

我们有一个应用程序,整体上用作"泛型"由许多不同客户提供的产品,通常具有可用于所有客户的相同功能。但是,有一些个性化的组件,特别是与客户的数据导入相关的组件。相应的内部系统。

到目前为止,这主要意味着将不同的输入格式转换为相同的数据结构,但是为某些类型保留其他客户特定数据并在代码的选定部分中再次使用它变得越来越重要。 #34;关于这个特定的客户。在整个申请过程中,这通常不会成为很多不同的地方。

没有"污染"核心模型类,"可扩展性"目前通过让有问题的类派生自DynamicObject,然后使用一些代码来动态处理用魔术字符串键入的属性,并在其他地方检查这些并检索它们来实现,这甚至不是类型安全的,因为你需要知道将可能的值转化为什么。

我对此感到不满意,并希望提出一种感觉有点像“猴子修补”的解决方案。并且在静态类型环境中更具惯用性。问题是实现这一目标的最佳选择是什么。 (另一个要求是,附加数据应该易于与对象的其余部分序列化/反序列化。)

  • "显而易见"面向对象语言中的东西当然是子类化模型类型,但是这会在对象创建和复制方面产生许多新问题,并且通常不是我喜欢的东西(它只是违反了#34;组合结束)继承"。)
  • 仍然使用继承但限制其影响的解决方案是向实际类添加单个属性ExtensionData,其类型为空*ExtensionData类,然后按客户进行子类化。使用它确实涉及对实际扩展数据对象进行某种类型检查和转换,但除此之外,它将完全是类型安全的。我认为这是我现在的首选方式。
  • 与目前使用的动态方法非常相似的东西将是使用Dictionary<string, object>替换上一个变体中的类型化对象。我认为它仍然比现在发生的更好,但它具有动态类型和使用魔术字符串的相同基本问题(当然,即使它们将被存储为常量)。

预计扩展数据实际上不是动态的;一旦知道导入了哪些客户数据,我们就会在一个明确定义的子域中,对于正在导入的数据中的内容没有任何意外。

(通常情况下,这在F#中会更容易,我可以使用范围扩展属性或ExtensionData为DU,其中每个案例都包含客户特定的记录类型,可以明确地识别通过模式匹配。)

什么是最常见的&#39;惯用的&#39;并且在C#中可以维护这种方式吗?上面提到的任何替代品都有巨大的优势或缺陷我还没考虑过吗?

2 个答案:

答案 0 :(得分: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>();
    }
}

限制是您只能为给定类型添加一个实例/属性。

如果性能不是问题,则另一种解决方案可能如下。 但要注意使用像这样的表达式很慢(呃)。你给它一个lambda,但隐式转换为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,新的nameof运算符应该比使用表达式快得多。但你会放松一些类型的安全。

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));
    }
}

编辑:

如果扩展属性/数据的数量不是那么多,另一个解决方案可能是使基本模型通用

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派生而不是每个独特的客户需求/数据

答案 1 :(得分:0)

我不确定最惯用的方式是什么,但我同意你的看法,我更喜欢使用聚合的解决方案。

如果你以特定的方式设置它,使用一组标记接口和帮助程序,最好每个(客户每个)接口只有一个具体的实现,你可以使这个相对类型安全,并通过拥有一些通用访问允许将此数据映射到例如的基础设施字典。

我创建了一个非常粗略的原型实现,只是为了说明一些想法。我将在此处发布重点,在此gist(约200行)中可以找到“完整”示例。

我们首先分别为可扩展类和扩展数据类提供两个标记接口,这些接口可以使用反射轻松发现。

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

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

我们将此更多类型保存并修复聚合关系:

// Making it type safe
public interface IExtendableOf<T> : IExtendable where T : class, IExtendable 
{
    IExtensionDataFor<T> ExtensionData { get; set; }
}

// Making it type safe & discoverable through reflection.
public interface IExtensionDataFor<T> : IExtensionData where T : class
{
}

接下来,我们定义一个接口,该接口由特定客户的特定扩展数据提供商实现。

// 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);
    }
}