你将如何实现一个“特征”,在C#设计模式?你将如何实现一个“特征”,在C#设计模式?(How wou

2019-05-14 11:22发布

我知道这个功能不会在C#中存在,但PHP最近增加了一个功能叫做特质 ,我认为是一个有点傻,在第一,直到我开始思考它。

说我有叫一个基类ClientClient有一个称为一个属性Name

现在我开发将由许多不同的客户可以使用可重复使用的应用程序。 所有客户的认同,客户端应该有一个名称,因此它在基类之中。

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

随着特征,我们可以做的重量和高度功能特点:

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

现在,我能满足我所有的客户的需求,而无需添加任何额外的绒毛类。 如果我的客户回来后,​​说:“哦,我真的很喜欢这个功能,我能得到它吗?”,我刚更新的类定义,包括额外的特征。

你将如何在C#中做到这一点?

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

(通过“顾客”,我的意思是谁已聘请我作为一个开发者,而由“客户端”我指的编程类文字的人,我的每一个客户都有客户,他们希望记录信息)

Answer 1:

您可以通过使用标记的接口和扩展方法获得的语法。

先决条件:接口需要定义稍后使用的扩展方法的合同。 基本上,接口定义了能够“实行”性状的合同; 理想情况下,你添加的接口的类应该已经存在,因此是不需要额外的实现接口的所有成员。

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
}

注意:通过实施IDynamicMetaObjectProvider以及对象甚至会允许通过DLR以暴露所述动态数据,使得获得透明的附加属性与使用时dynamic关键字。



Answer 2:

C# 语言 (至少5版)不具有的性状的支持。

然而,Scala有特点和Scala在JVM上运行(和CLR)。 因此,它不是的运行时间的问题,而只是意味着该语言。

考虑到性状,至少在斯卡拉意义上可以认为是“相当神奇编译代理方法”(它们影响MRO,这是不同于混入用Ruby)。 在C#中实现这个行为的方法是使用接口和“大量的手工代理方法”(如组成)。

这个繁琐的过程可以用一个假设的处理器来实现(也许是自动代码生成用于通过模板部分类?),但是这并不是C#。

编码愉快。



Answer 3:

我想指出NRoles ,C#中的角色 ,其中角色类似特征的实验。

NRoles使用后的编译器重写IL和方法注入到一类。 这使您可以编写代码这样的:

public class RSwitchable : Role
{
    private bool on = false;
    public void TurnOn() { on = true; }
    public void TurnOff() { on = false; }
    public bool IsOn { get { return on; } }
    public bool IsOff { get { return !on; } }
}

public class RTunable : Role
{
    public int Channel { get; private set; }
    public void Seek(int step) { Channel += step; }
}

public class Radio : Does<RSwitchable>, Does<RTunable> { }

其中类Radio实现RSwitchableRTunable 。 在幕后, Does<R>是不带任何成员的接口,所以基本上Radio编译成一个空的类。 编译后的IL重写注入的方法RSwitchableRTunableRadio ,因为如果它确实从两个角色衍生然后可以使用(从另一个组件):

var radio = new Radio();
radio.TurnOn();
radio.Seek(42);

要使用radio直接重写之前发生(即,在为其中同一组件Radio类型声明),则不得不求助于扩展方法As<R>

radio.As<RSwitchable>().TurnOn();
radio.As<RTunable>().Seek(42);

因为编译器将不允许调用TurnOnSeek直接在Radio类。



Answer 4:

有一个学术项目,由斯特凡Reichart从软件集团的组成伯尔尼(瑞士)大学,它提供了一个真正实现性状的C#语言开发。

看看上CSharpT论文(PDF),为他做了什么,基于单编译器的完整描述。

这里是什么可以写一个例子:

trait TCircle
{
    public int Radius { get; set; }
    public int Surface { get { ... } }
}

trait TColor { ... }

class MyCircle
{
    uses { TCircle; TColor }
}


Answer 5:

这实在是一个建议扩展卢塞罗的答案在所有存储在基类。

如何使用依赖属性呢?

这将有可能使客户端类的重量轻的,当你有很多属性未总是由每个后代设置运行时间的影响。 这是因为值存储在静态成员。

using System.Windows;

public class Client : DependencyObject
{
    public string Name { get; set; }

    public Client(string name)
    {
        Name = name;
    }

    //add to descendant to use
    //public double Weight
    //{
    //    get { return (double)GetValue(WeightProperty); }
    //    set { SetValue(WeightProperty, value); }
    //}

    public static readonly DependencyProperty WeightProperty =
        DependencyProperty.Register("Weight", typeof(double), typeof(Client), new PropertyMetadata());


    //add to descendant to use
    //public double Height
    //{
    //    get { return (double)GetValue(HeightProperty); }
    //    set { SetValue(HeightProperty, value); }
    //}

    public static readonly DependencyProperty HeightProperty =
        DependencyProperty.Register("Height", typeof(double), typeof(Client), new PropertyMetadata());
}

public interface IWeight
{
    double Weight { get; set; }
}

public interface IHeight
{
    double Height { get; set; }
}

public class ClientA : Client, IWeight
{
    public double Weight
    {
        get { return (double)GetValue(WeightProperty); }
        set { SetValue(WeightProperty, value); }
    }

    public ClientA(string name, double weight)
        : base(name)
    {
        Weight = weight;
    }
}

public class ClientB : Client, IHeight
{
    public double Height
    {
        get { return (double)GetValue(HeightProperty); }
        set { SetValue(HeightProperty, value); }
    }

    public ClientB(string name, double height)
        : base(name)
    {
        Height = height;
    }
}

public class ClientC : Client, IHeight, IWeight
{
    public double Height
    {
        get { return (double)GetValue(HeightProperty); }
        set { SetValue(HeightProperty, value); }
    }

    public double Weight
    {
        get { return (double)GetValue(WeightProperty); }
        set { SetValue(WeightProperty, value); }
    }

    public ClientC(string name, double weight, double height)
        : base(name)
    {
        Weight = weight;
        Height = height;
    }

}

public static class ClientExt
{
    public static double HeightInches(this IHeight client)
    {
        return client.Height * 39.3700787;
    }

    public static double WeightPounds(this IWeight client)
    {
        return client.Weight * 2.20462262;
    }
}


Answer 6:

建设什么卢塞罗建议 ,我想出了这一点:

internal class Program
{
    private static void Main(string[] args)
    {
        var a = new ClientA("Adam", 68);
        var b = new ClientB("Bob", 1.75);
        var c = new ClientC("Cheryl", 54.4, 1.65);

        Console.WriteLine("{0} is {1:0.0} lbs.", a.Name, a.WeightPounds());
        Console.WriteLine("{0} is {1:0.0} inches tall.", b.Name, b.HeightInches());
        Console.WriteLine("{0} is {1:0.0} lbs and {2:0.0} inches.", c.Name, c.WeightPounds(), c.HeightInches());
        Console.ReadLine();
    }
}

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

    public Client(string name)
    {
        Name = name;
    }
}

public interface IWeight
{
    double Weight { get; set; }
}

public interface IHeight
{
    double Height { get; set; }
}

public class ClientA : Client, IWeight
{
    public double Weight { get; set; }
    public ClientA(string name, double weight) : base(name)
    {
        Weight = weight;
    }
}

public class ClientB : Client, IHeight
{
    public double Height { get; set; }
    public ClientB(string name, double height) : base(name)
    {
        Height = height;
    }
}

public class ClientC : Client, IWeight, IHeight
{
    public double Weight { get; set; }
    public double Height { get; set; }
    public ClientC(string name, double weight, double height) : base(name)
    {
        Weight = weight;
        Height = height;
    }
}

public static class ClientExt
{
    public static double HeightInches(this IHeight client)
    {
        return client.Height * 39.3700787;
    }

    public static double WeightPounds(this IWeight client)
    {
        return client.Weight * 2.20462262;
    }
}

输出:

Adam is 149.9 lbs.
Bob is 68.9 inches tall.
Cheryl is 119.9 lbs and 65.0 inches.

这是不是很漂亮,我想,但无论是不是太糟糕了。



Answer 7:

性状可以在C#8通过使用默认接口的方法来实现。 Java的8引入这个原因默认接口的方法了。

使用C#8,你可以写几乎正是你在问题中提出的。 性状是由为他们的方法的默认实现的IClientWeight,IClientHeight接口来实现的。 在这种情况下,他们只返回0:

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

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

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

ClientAClientB有特点,但不执行。 ClientC仅实现IClientHeight并返回一个异数,在这种情况下,16:

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

getHeight()被调用ClientB通过该接口,默认实现调用。 getHeight()只能通过接口来调用。

ClientC实现IClientHeight接口,这样它自己的方法被调用。 该方法可通过类本身。

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

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

此SharpLab.io例子示出了从本实施例制造的代码

许多中所描述的性状特征上的特征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链接此示例。



Answer 8:

这听起来像PHP的版本面向方面编程的。 有工具来帮助像PostSharp或MS团结在某些情况下。 如果你想滚你自己,用C#代码的属性注射是一种方法,或者建议的扩展方法有限的情况下。

实际上取决于你想多么复杂获得。 如果你正在试图建立复杂的东西我会看一些这些工具的帮助。



文章来源: How would you implement a “trait” design-pattern in C#?