由于开闭原则处理继承层次结构

Posted

技术标签:

【中文标题】由于开闭原则处理继承层次结构【英文标题】:Dealing with inheritance hierarchy because of Open-Closed Principle 【发布时间】:2019-01-21 10:43:41 【问题描述】:

当我尝试遵循Open-Closed Principle (OCP) 时,在实现了许多用例之后,我总是以继承类的层次结构告终。通常它发生在 MVVM 结构中的 ViewModel 上,因为它们发生了很大的变化。例如(C#,但可以是任何其他面向类的语言):

internal class MyAwesomeViewModel

    private IDependency1 _dependency1;

    public string Property1  get; set; 
    public ICommand Command1  get; 

    public MyAwesomeViewModel(IDependency1 dependency1)
    
        _dependency1 = dependency1;
        Command1 = new DelegateCommand(...);
    


internal class MyAwesomeViewModelWithAnotherProperty: MyAwesomeViewModel

    public string Property2  get; set; 

    public MyAwesomeViewModelWithAnotherProperty(IDependency1 dependency1)
        :base(dependency1)
    
    


internal class MyAwesomeViewModelWithNewCommandAndDependency: MyAwesomeViewModelWithAnotherProperty

    private IDependency2 _dependency2;

    public ICommand Command2;

    public MyAwesomeViewModelWithNewCommandAndDependency(IDependency1 dependency1, IDependency2 dependency2)
    : base(dependency1)
    
        _dependency2 = dependency2;
        Command2 = new DelegateCommand(...);
    


internal class MyAwesomeViewModelWithBunchNewProperties : MyAwesomeViewModelWithNewCommandAndDependency

    public int IntProperty  get; 
    public bool BoolProperty  get; 
    public double DoubleProperty  get; 

    public MyAwesomeViewModelWithBunchNewProperties(IDependency1 dependency1, IDependency2 dependency2)
    : base(dependency1, dependency2)
    
    

问题是,当涉及到 4 级或更多级别的继承深度时,它变得非常混乱且难以阅读。另外我总是遇到命名问题,这是错误组合的信号(如MainWindowViewModel,然后是MainWindowViewModelCloseCommand,然后是MainWindowViewModelUserRelatedProperties等等)。

由于只使用了最后一个派生类(上例中的MyAwesomeViewModelWithBunchNewProperties),有时我会考虑将发布之前的所有继承或其他重要里程碑压缩到 1 个类中,如下所示:

internal class MyAwesomeViewModel

    public int IntProperty  get; 
    public bool BoolProperty  get; 
    public double DoubleProperty  get; 
    public string Property1  get; set; 
    public string Property2  get; set; 
    public ICommand Command1  get; 
    public ICommand Command2;

    public MyAwesomeViewModelWithBunchNewProperties(IDependency1 dependency1, IDependency2 dependency2)
    
        _dependency1 = dependency1;
        _dependency2 = dependency2;
        Command1 = new DelegateCommand(...);
        Command2 = new DelegateCommand(...);
    

但它可能违反单一职责原则 (SRP) 并导致超大类。

问题:如何处理大量继承问题?还是完全没有问题,有这种类结构就可以了?

【问题讨论】:

坚持核心域中重要的 SOLID。您真的需要 ViewModel 中一流的 OO 代码或与视图相关的任何内容吗?有人可能会争论,但对我来说肯定不是。我对核心域进行了非常彻底的单元测试,但不是视图模型。我用 E2E 测试介绍了这一点。不过,我总是尝试坚持一些原则,例如 DIP 和 SRP。 请记住接受对您最有帮助的答案:)。如果您需要任何帮助,我也可以进一步澄清。 【参考方案1】:

组合优于继承!

很多时候,开发人员只关注 SOLID 之类的原则,却忘记了基本的组合优于继承原则。

首先要记住的是,继承将您紧密地绑定到基类。这会导致诸如拒绝遗赠(违反 Liskov 替代原则)等问题。

当我们谈到 OOP 中的类时,我们定义与数据相关联的行为,而不是对象。 当我们根据问题试图实现的行为对问题进行建模时,我们可以获得小的构建块.

您可以将 MyAwesomeViewModel 中的核心行为定义为可以在其他类中引用的小类。通过这种方式,您可以轻松组合 MyAwesomeViewModelWithBunchNewProperties 等对象。

关于单一职责原则,这是一个非常被误解的原则。 SRP 指出,共同存在的行为应该一起改变。这意味着一组相互依赖并且将一起改变的行为属于同一类。

关于您的特定场景,视图模型通常可能无法从组合或继承中受益。视图模型是数据传输对象(DTO),它们不捕获行为。这里的代码重复很容易被忽视\可以接受。如果代码重复在您的视图模型中造成问题,只需从其他 DTO 组合它们

【讨论】:

【参考方案2】:

为了使 OCP 为您工作,您应该确定您的客户需要的行为。这些知识可用于构建您的 ViewModel 类然后实现的一个或多个接口。为了避免你的继承层次结构,你可以更喜欢组合来构建你的 ViewModels

【讨论】:

【参考方案3】:

SOLID 原则是帮助您提出更好解决方案的想法/指南。遵循这些原则并没有固有地获得任何好处。

此处发布的继承策略效果不佳。它一无所获,并导致混乱和更多的工作。这不是一个好方法。

但它可能违反单一职责原则 (SRP) 并导致超大类。

就什么是“单一职责”而言,SRP 非常含糊。您可以根据需要将其定义为窄或宽。同样,该原则只是为了指导您并让您考虑不要不必要地混合应该分开的东西。

在这里,你可以说“这个类的职责是成为数据绑定视图的模型。”

超类

同样的。这只是一个指导方针。你永远不能说“一个类最多只能有 N 个成员”。这个建议对于任何 N 都是错误的,因为 N 是上下文相关的。

【讨论】:

以上是关于由于开闭原则处理继承层次结构的主要内容,如果未能解决你的问题,请参考以下文章

Façade 是不是利用了开闭原则?

开闭原则

前端用到的设计模式之开闭原则. 里氏代换原则

设计模式--6大原则--开闭原则

开闭原则

LARAVEL:如何使用 SOLID 原则的开闭原则?