c# - 你将如何在 C# 中实现 "trait"设计模式?

标签 c# design-patterns traits code-reuse default-interface-member

我知道 C# 中不存在此功能,但 PHP 最近添加了一个名为 Traits 的功能一开始我觉得这有点傻,直到我开始考虑它。

假设我有一个名为 Client 的基类。 Client 有一个名为 Name 的属性。

现在我正在开发一个可重复使用的应用程序,它将被许多不同的客户使用。所有客户都同意客户应该有一个名字,因此它在基类中。

现在客户 A 过来说他还需要跟踪客户的体重。客户 B 不需要体重,但他想跟踪高度。客户 C 想要跟踪体重和高度。

有了 traits,我们可以让 Weight 和 Height 特征都成为 traits:

class ClientA extends Client use TClientWeight
class ClientB extends Client use TClientHeight
class ClientC extends Client use TClientWeight, TClientHeight

现在我可以满足所有客户的需求,而无需在类(class)中添加任何额外的内容。如果我的客户稍后回来说“哦,我真的很喜欢那个功能,我也可以拥有它吗?”,我只需更新类定义以包含额外的特征。

您将如何在 C# 中完成此操作?

接口(interface)在这里不起作用,因为我需要属性和任何关联方法的具体定义,并且我不想为类的每个版本重新实现它们。

(“客户”是指雇用我作为开发人员的字面上的人,而“客户”是指编程类(class);我的每个客户都有他们想要记录其信息的客户)

最佳答案

您可以使用标记接口(interface)和扩展方法来获取语法。

先决条件:接口(interface)需要定义扩展方法稍后使用的契约。基本上,接口(interface)定义了能够“实现”特征的契约;理想情况下,您添加接口(interface)的类应该已经包含接口(interface)的所有成员,这样不需要额外的实现。

public class Client {
  public double Weight { get; }

  public double Height { get; }
}

public interface TClientWeight {
  double Weight { get; }
}

public interface TClientHeight {
  double Height { get; }
}

public class ClientA: Client, TClientWeight { }

public class ClientB: Client, TClientHeight { }

public class ClientC: Client, TClientWeight, TClientHeight { }

public static class TClientWeightMethods {
  public static bool IsHeavierThan(this TClientWeight client, double weight) {
    return client.Weight > weight;
  }
  // add more methods as you see fit
}

public static class TClientHeightMethods {
  public static bool IsTallerThan(this TClientHeight client, double height) {
    return client.Height > height;
  }
  // add more methods as you see fit
}

像这样使用:

var ca = new ClientA();
ca.IsHeavierThan(10); // OK
ca.IsTallerThan(10); // compiler error

编辑:提出了如何存储额外数据的问题。这也可以通过做一些额外的编码来解决:

public interface IDynamicObject {
  bool TryGetAttribute(string key, out object value);
  void SetAttribute(string key, object value);
  // void RemoveAttribute(string key)
}

public class DynamicObject: IDynamicObject {
  private readonly Dictionary<string, object> data = new Dictionary<string, object>(StringComparer.Ordinal);

  bool IDynamicObject.TryGetAttribute(string key, out object value) {
    return data.TryGet(key, out value);
  }

  void IDynamicObject.SetAttribute(string key, object value) {
    data[key] = value;
  }
}

然后,如果“特征接口(interface)”继承自 IDynamicObject,特征方法可以添加和检索数据:

public class Client: DynamicObject { /* implementation see above */ }

public interface TClientWeight, IDynamicObject {
  double Weight { get; }
}

public class ClientA: Client, TClientWeight { }

public static class TClientWeightMethods {
  public static bool HasWeightChanged(this TClientWeight client) {
    object oldWeight;
    bool result = client.TryGetAttribute("oldWeight", out oldWeight) && client.Weight.Equals(oldWeight);
    client.SetAttribute("oldWeight", client.Weight);
    return result;
  }
  // add more methods as you see fit
}

注意:通过实现 IDynamicMetaObjectProvider此外,该对象甚至允许通过 DLR 公开动态数据,从而在与 dynamic 关键字一起使用时使对其他属性的访问变得透明。

关于c# - 你将如何在 C# 中实现 "trait"设计模式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10729230/

相关文章:

c# - 在 C# 中使用 fastJson 解析 JSON

c# - 在 C# 中 move 文件

oop - 关于开放/封闭主体的困惑

c# - DDD 中的服务和存储库 (C#)

scala - "traits allow composition"是什么?

c# - 如何更改禁用控件的样式?

c# - 反射是 C# 中系统命名空间内的嵌套命名空间吗?

java - Duck 实例策略模式 - Head first 设计模式

php - 使用特征将代码从一个大类拆分为多个文件怎么样?

rust - 选择冲突 trait 实现的首选实现(使用负边界)