你如何在C#中实现 "trait "设计模式?[英] How would you implement a "trait" design-pattern in C#?

本文是小编为大家收集整理的关于你如何在C#中实现 "trait "设计模式?的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我知道该功能在C#中不存在,但是PHP最近添加了一个称为特征我刚开始思考的时候有点愚蠢.

说我有一个名为Client的基类. Client有一个称为Name的单个属性.

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

现在,客户A来了,并说他还需要跟踪客户的体重.客户B不需要体重,但他想跟踪身高.客户C想要跟踪体重和身高.

具有特征,我们可以使体重和高度特征特征特征:

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

现在,我可以满足所有客户的需求,而无需在课堂上增加任何额外的绒毛.如果我的客户稍后再回来说:"哦,我真的很喜欢该功能,我也可以吗?",我只是更新类定义以包括额外的特质.

您将如何在C#?

中完成此操作

接口在这里不起作用,因为我想要属性和任何相关方法的具体定义,并且我不想重新实现该类的每个版本.

(由"客户",我的意思是一个雇用我为开发人员的字面人员,而"客户"我是指编程课;我的每个客户都有他们想要记录信息的客户)

推荐答案

您可以使用标记接口和扩展方法获得语法.

先决条件:接口需要定义合同,该合同后来由扩展方法使用.基本上,界面定义了能够"实现"特征的合同;理想情况下,您添加界面的类应该已经存在所有接口的成员,以便 no 需要附加实现.

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

,然后,如果"特质接口"从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
}

注意:通过实施允许通过DLR公开动态数据,与dynamic关键字一起使用时对附加属性的访问.

其他推荐答案

可以通过使用默认接口方法在C#8中实现特征. Java 8也引入了默认接口方法.

使用C#8,您几乎可以编写问题中提出的内容.这些特征是由iClient量级,iClientHeight接口实现的,这些接口为其方法提供了默认实现.在这种情况下,他们只是返回0:

public interface IClientWeight
{
    int getWeight()=>0;
}

public interface IClientHeight
{
    int getHeight()=>0;
}

public class Client
{
    public String Name {get;set;}
}

ClientA和ClientB具有特征,但不要实现它们. ClientC仅实施IClientHeight并返回不同的数字,在这种情况下16:

class ClientA : Client, IClientWeight{}
class ClientB : Client, IClientHeight{}
class ClientC : Client, IClientWeight, IClientHeight
{
    public int getHeight()=>16;
}

当通过接口在ClientB中调用getHeight()时,将调用默认实现. getHeight()只能通过接口调用.

clientc实现了iClientheight接口,因此调用了自己的方法.该方法可通过课程本身获得.

public class C {
    public void M() {        
        //Accessed through the interface
        IClientHeight clientB = new ClientB();        
        clientB.getHeight();

        //Accessed directly or through the class
        var clientC = new ClientC();        
        clientC.getHeight();
    }
}

This SharpLab.io example shows the code produced from this示例

php php概述中可以通过默认接口方法轻松实现.特征(接口)可以组合.也可以定义摘要方法迫使类实现某些要求.

假设我们希望我们的特征具有sayHeight()和sayWeight()的方法,该方法返回具有高度或重量的字符串.他们需要某种方法来强制展示课程(从PHP指南中被盗​​)才能实现一种返回身高和体重的方法:

public interface IClientWeight
{
    abstract int getWeight();
    String sayWeight()=>getWeight().ToString();
}

public interface IClientHeight
{
    abstract int getHeight();
    String sayHeight()=>getHeight().ToString();
}

//Combines both traits
public interface IClientBoth:IClientHeight,IClientWeight{}

现在的客户端 可以实现getHeight()或getWeight()方法,但不需要了解say方法.

这提供了一种更干净的装饰方式

sharplab.io链接对于此样本.

其他推荐答案

c#语言(至少对第5版)不支持特征.

但是,Scala具有特征,并且在JVM(和CLR)上运行Scala.因此,这不是运行时的问题,而是语言的问题.

认为,至少从Scala意义上讲,特征可以被认为是"以代理方法编译的相当魔术"(它们不影响MRO,这与Ruby中的Mixins不同).在C#中获得此行为的方法是使用界面和"许多手动代理方法"(例如组成).

这个繁琐的过程可以使用假设的处理器(也许是通过模板为部分类别的自动代码生成?),但这不是C#.

.

快乐的编码.

本文地址:https://www.itbaoku.cn/post/627749.html

问题描述

I know the feature doesn't exist in C#, but PHP recently added a feature called Traits which I thought was a bit silly at first until I started thinking about it.

Say I have a base class called Client. Client has a single property called Name.

Now I'm developing a re-usable application that will be used by many different customers. All customers agree that a client should have a name, hence it being in the base-class.

Now Customer A comes along and says he also need to track the client's Weight. Customer B doesn't need the Weight, but he wants to track Height. Customer C wants to track both Weight and Height.

With traits, we could make the both the Weight and the Height features traits:

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

Now I can meet all my customers' needs without adding any extra fluff to the class. If my customer comes back later and says "Oh, I really like that feature, can I have it too?", I just update the class definition to include the extra trait.

How would you accomplish this in C#?

Interfaces don't work here because I want concrete definitions for the properties and any associated methods, and I don't want to re-implement them for each version of the class.

(By "customer", I mean a literal person who has employed me as a developer, whereas by "client" I'm referring a programming class; each of my customers has clients that they want to record information about)

推荐答案

You can get the syntax by using marker interfaces and extension methods.

Prerequisite: the interfaces need to define the contract which is later used by the extension method. Basically the interface defines the contract for being able to "implement" a trait; ideally the class where you add the interface should already have all members of the interface present so that no additional implementation is required.

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
}

Use like this:

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

Edit: The question was raised how additional data could be stored. This can also be addressed by doing some extra coding:

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

And then, the trait methods can add and retrieve data if the "trait interface" inherits from 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
}

Note: by implementing IDynamicMetaObjectProvider as well the object would even allow to expose the dynamic data through the DLR, making the access to the additional properties transparent when used with the dynamic keyword.

其他推荐答案

Traits can be implemented in C# 8 by using default interface methods. Java 8 introduced default interface methods for this reason too.

Using C# 8, you can write almost exactly what you proposed in the question. The traits are implemented by the IClientWeight, IClientHeight interfaces that provide a default implementation for their methods. In this case, they just return 0:

public interface IClientWeight
{
    int getWeight()=>0;
}

public interface IClientHeight
{
    int getHeight()=>0;
}

public class Client
{
    public String Name {get;set;}
}

ClientA and ClientB have the traits but don't implement them. ClientC implements only IClientHeight and returns a different number, in this case 16 :

class ClientA : Client, IClientWeight{}
class ClientB : Client, IClientHeight{}
class ClientC : Client, IClientWeight, IClientHeight
{
    public int getHeight()=>16;
}

When getHeight() is called in ClientB through the interface, the default implementation is called. getHeight() can only be called through the interface.

ClientC implements the IClientHeight interface so its own method is called. The method is available through the class itself.

public class C {
    public void M() {        
        //Accessed through the interface
        IClientHeight clientB = new ClientB();        
        clientB.getHeight();

        //Accessed directly or through the class
        var clientC = new ClientC();        
        clientC.getHeight();
    }
}

This SharpLab.io example shows the code produced from this example

Many of the traits features described in the PHP overview on traits can be implemented easily with default interface methods. Traits (interfaces) can be combined. It's also possible to define abstract methods to force classes to implement certain requirements.

Let's say we want our traits to have sayHeight() and sayWeight() methods that return a string with the height or weight. They'd need some way to force exhibiting classes (term stolen from the PHP guide) to implement a method that returns the height and weight :

public interface IClientWeight
{
    abstract int getWeight();
    String sayWeight()=>getWeight().ToString();
}

public interface IClientHeight
{
    abstract int getHeight();
    String sayHeight()=>getHeight().ToString();
}

//Combines both traits
public interface IClientBoth:IClientHeight,IClientWeight{}

The clients now have to implement thet getHeight() or getWeight() method but don't need to know anything about the say methods.

This offers a cleaner way to decorate

SharpLab.io link for this sample.

其他推荐答案

C# language (at least to version 5) does not have support for Traits.

However, Scala has Traits and Scala runs on the JVM (and CLR). Therefore, it's not a matter of run-time, but simply that of the language.

Consider that Traits, at least at the Scala sense, can be thought of as "pretty magic to compile in proxy methods" (they do not affect the MRO, which is different from Mixins in Ruby). In C# the way to get this behavior would be to use interfaces and "lots of manual proxy methods" (e.g. composition).

This tedious process could be done with a hypothetical processor (perhaps automatic code generation for a partial class via templates?), but that's not C#.

Happy coding.