如何在C#中实现类之间的共享行为(没有课程的多重继承)

时间:2009-04-29 23:09:23

标签: c# design-patterns inheritance multiple-inheritance code-reuse

更新: 所以这里的每个人都告诉我,我只需要重新开始我如何设计课程(顺便说一句,谢谢大家的优秀答案!)。接下来,我开始对strategy pattern进行广泛的阅读。我想创建从抽象基类继承的行为类(或策略类)。然后,Candidate类将具有不同抽象基类/类的属性作为行为或策略的Type。也许是这样的:

public abstract class SalaryStrategy {
    public abstract decimal Salary { get; set; }
    public abstract decimal Min { get; set; }
    public abstract decimal Mid { get; set; }
    public decimal CompaRatio {
        get {
            if (this.Mid == 0) { return 0; }
            else { return this.Salary / this.Mid; }
        }
    }
}

public class InternalCurrentSalaryStrategy {
    public override decimal Salary { get; set; }
    public override decimal Min {
        get { return this.Salary * .25m; }
        set { }
    }
    public override decimal Mid { get; set; }
}

public class Candidate {
    public int Id { get; set; }
    public string Name { get; set; }
    public SalaryStrategy CurrentSalaryStrategy { get; set; }
}

public static void Main(string[] args) {
    var internal = new Candidate();
    internal.CurrentSalaryStrategy = new InternalCurrentSalaryStrategy();
    var internalElp = new Candidate();
    internalElp.CurrentSalaryStrategy = new InternalCurrentSalaryStrategy();
    var elp = new Candidate();
    // elp.CurrentSalaryStrategy can stay null cause it's not used for elps
}

有任何意见或建议吗?


原文问题:

我正在努力学习并更加精通设计模式和原则。我目前正在为一些难以理解的课程设计。这是代码的一个非常简洁的版本:

public class Candidate {
    public int Id { get; set; }
    public string Comments { get; set; }
    // lots more properties and behaviors...
}

public class InternalCandidate : Candidate {
    public decimal CurrentMid { get; set; }
    public decimal CurrentMax {
         get { return this.CurrentMin * 1.3m;
    }
    // lots more properties and behaviors...
}

public class EntryLevelCandidate : Candidate {
    public string Gpa { get; set; }
    // lots more properties and behaviors...
}

public class InternalEntryLevelCandidate /* what do I inherit here??? */ {
    // needs all of the properties and behaviors of
    // EntryLevelCandidate but also needs the CurrentMin and
    // CurrentMax (and possibly more) in InternalCandidate
}

InternalEntryLevelCandidate类主要是EntryLevelCandidate,但需要共享InternalCandidate的一些实现。我说实现是因为我不希望实现不同或重复,否则我会使用一个接口来进行常见的契约,并在每个类中都有具体的实现。 InternalCandidate属性和行为的某些实现需要是通用的或共享的。我读过有关C ++和Ruby mixins的内容,它们似乎与我想要做的类似。我还阅读了这篇有趣的博客文章,其中讨论了一种行为类型的想法,其中一个类能够继承多个行为,同时仍然保持一个“是一个”关系:http://www.deftflux.net/blog/post/A-good-design-for-multiple-implementation-inheritance.aspx。这似乎传达了我想要的东西。任何人都可以就如何使用良好的设计实践来实现这一目标给我一些指导吗?

5 个答案:

答案 0 :(得分:5)

不可变数据值类。如果各种候选子类中的任何属性代表某种有意义的数据值,请为其创建一个不可变类,并提供您需要的行为。然后,每个不同的Candidate子类都可以使用数据类型,但您的代码仍然封装在数据类中。

扩展方法。这些可以重载以适用于任何类。

我会避免使用装饰器模式并坚持编译/反射功能。

组合。立即在不同的类中开发独特的行为,并围绕它们构建候选类,而不是在候选类中编写独特的行为并尝试提取其功能以用于相关的上课。

根据您使用类的方式,您还可以实现并使用显式和隐式转换运算符到相关类型,因此您不必重新实现接口(您想要避免),而是实际将对象转换为无论出于何种目的,您需要的类型/实现。

我刚想到的另一件事,与最后一段相关,是有一个租赁系统,你的类产生和适当类型的对象,允许它被操纵,然后消耗它以吸收更新的信息。

答案 1 :(得分:4)

这是一篇关于这个主题的学术论文,我觉得这很有趣(PDF link)。

但是,我认为您正试图在您的概括中强加业务逻辑。你碰巧知道一个InternalCandidate永远不会看到他的GPA。但是,InternalCandidate肯定有GPA。所以,你已经破解了这个叫做InternalEntryLevelCandidate的怪人,因为你碰巧知道你想看看这个人的GPA。在架构上,我认为EntryLevelCandidate是不正确的。我会给候选人添加一个“等级”概念并给他一个GPA。由业务逻辑来决定他们是否查看GPA。

编辑:此外,Scott Meyers在他的书中很好地解决了这个问题。

答案 2 :(得分:2)

免责声明

根据我的经验,需要多重继承是例外而不是规则,仔细设计类层次结构通常可以避免需要此功能。我同意JP的意见,你的样本可以避免这个要求。

回到这个问题,没有干净的解决方案,但是你有几个选择:

  • 使用扩展方法,有点右击Resolve不起作用的缺点,也有些人真的不喜欢这些小狗。

  • 创建一个聚合对象,该对象包含您想要合成的每个类的实例,并重新实现委派的存根方法。

  • 为每个行为定义一个接口,并在执行行为之前让基础中的方法检查this is IInterface。 (允许您将行为定义提取到基础)

近似重复: Multiple inheritance in C#

答案 3 :(得分:1)

我同意继承似乎不是正确的事情。我不确定我是否知道完美的答案,但也许Decorator pattern是合适的。

另一个更深奥的想法是考虑面向方面的编程。你可以用方面做一些非常棒的事情,但这是一个非常高级的话题,我还没有掌握。那些人就像Rikard Oberg和他的Qi4J同伙。

答案 4 :(得分:0)

我只是使用Delegation pattern。最终,我会为每个不同的功能使用一个接口,然后将一个具体的类作为每个接口的委托。然后你的最终类只使用他们需要的委托,并且可以从多个接口继承。

public class InternalEntryLevelCandidate : EntryLevelCandidate {
    private  InternalCandidate internalCandidateDelegate
        = new InternalCandidate();

    public decimal CurrentMid { 
        get { return internalCandidateDelegate.CurrentMid; }
        set { internalCandidateDelegate.CurrentMid = value; }
    }

    public decimal CurrentMax {
        get { return internalCandidateDelegate.CurrentMax }
    }
}