zoukankan      html  css  js  c++  java
  • 面向对象思想的头脑风暴(二)—— 详解继承与组合的优缺点

    组合与继承都是提高代码可重用性的手段。在设计对象模型时,可以按照语义来识别类之间的组合关系和继承关系。在有些情况下,采用组合关系或者继承关系能完成同样的任务,组合和继承存在着对应关系:组合中的整体类和继承中的子类对应,组合中的局部类和继承中的父类对应,如下图:

    组合:继承:

    一、基础知识

    我们先用代码帮大家来理解一下组合和继承:

    1、对于已经存在Parent类时需要扩展其方法时

    结构图:

    继承代码:

    代码
    class Parent
        {
            
    public void Method1() 
            { 
            }
            
    public void Method2() { }
            
    public void Method3() { }
        }
     
    class Child1:Parent
        {
            
    public void MethodA() { }
        }
     
    class Child2:Parent
        {
            
    public void MethodA() { }
        }

    组合代码:

    代码
    class ComponentA
        {
            Parent p 
    = new Parent();
            
    public void Method2()
            {
                p.Method2();
            }
            
    public void MethodA()
            {
            }
        }
     
    class ComponentB
        {
            Parent p 
    = new Parent();
            
    public void Method2() {
                p.Method2();
            }
            
    public void MethodB()
            { 
            }
        }

    2、如果发现两个类具有很多代码相同的类需要抽象时,如下图A,B两个类,这两个类中method1,和method3两个方法代码相同

    继承:

    实现代码:

    代码
    class A:C
        {
            
    public void MethodB() { }
            
    public override void Method2()
            {
                
            }
        }
        
    class B:C
        {
            
    public override void Method2()
            {
                
            }
            
    public void MethodB() { }
        }
        
    class C
        {
            
    public void Method1() { }
            
    public virtual void Method2() { }
            
    public void Method3() { }
        }

    组合:

    实现代码:

    代码
    class A
        {
            
    public void MethodA() { }
            C c 
    = new C();
            
    public void Method1()
            {
                c.Method1();
            }
            
    public void Method2() { }

        }
        
    class B
        {
            
    public void MethodB() { }
            
    public void Method2() { }
        }
        
    class C
        {
            
    public void Method1() { }
            
    public void Method2() { }
            
    public void Method3() { }
        }

    二、继承与组合的优缺点

    合 关 系

    继 承 关 系

    优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立

    缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性

    优点:具有较好的可扩展性

    缺点:支持扩展,但是往往以增加系统结构的复杂度为代价

    优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象

    缺点:不支持动态继承。在运行时,子类无法选择不同的父类

    优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口

    缺点:子类不能改变父类的接口

    缺点:整体类不能自动获得和局部类同样的接口

    优点:子类能自动继承父类的接口

    缺点:创建整体类的对象时,需要创建所有局部类的对象

    优点:创建子类的对象时,无须创建父类的对象

    1、为什么继承破坏封装性:

     

    鸭子中不想要“飞”的方法,但因为继承无法封装这个无用的“飞”方法

    2、为什么继承紧耦合:

     

    当作为父类的BaseTable感觉Insert这个名字不合适时,如果希望将其修改成Create方法,那使用了子类对象Insert方法将会编译出错,可能你会觉得这改起来还算容易,因为有重构工具一下子就好了并且编译错误改起来很容易。但如果BaseTable和子类在不同的程序集中,维护的人员不同,BaseTable程序集升级,那本来能用的代码忽然不能用了,这还是很难让人接受的

    3、为什么继承扩展起来比较复杂

    当图书和数码的算税方式和数码产品一样时,而消费类产品的算税方式是另一样时,如果采用继承方案可能会演变成如下方式:

    这样如果产品继续增加,算税方式继续增加,那继承的层次会非常复杂,而且很难控制,而使用组合就能很好的解决这个问题,参见:面向对象思想的头脑风暴(一)

    4、继承不能支持动态继承

    这个其实很好理解,因为继承是编译期就决定下来的,无法在运行时改变,如3例中,如果用户需要根据当地的情况选择计税方式,使用继承就解决不了,而使用组合结合反射就能很好的解决。

    5、为什么继承 子类不能改变父类接口

    如2中的图

    子类中觉得Insert方法不合适,希望使用Create方法,因为继承的原因无法改变

     

    三、如何使用继承

     

    1、精心设计专门用于被继承的类,继承树的抽象层应该比较稳定,一般不要多于三层。

    2、对于不是专门用于被继承的类,禁止其被继承。

    3、优先考虑用组合关系来提高代码的可重用性。

    4、子类是一种特殊的类型,而不只是父类的一个角色

    5、子类扩展,而不是覆盖或者使父类的功能失效

     

    四、组合的缺点:

    1、整体类不能自动获得和局部类同样的接口

    如果父类的方法子类中几乎都要暴露出去,这时可能会觉得使用组合很不方便,使用继承似乎更简单方便。但从另一个角度讲,实际上也许子类中并不需要暴露这些方法,客户端组合应用就可以了。所以上边推荐不要继承那些不是为了继承而设计的类,一般为了继承而设计的类都是抽象类。

    2、创建整体类的对象时,需要创建所有局部类的对象

    这个可能没什么更好的办法,但在实际应用中并没有多出多少代码。

    五、相关原则

    1、里氏代换原则(LSP)(以下转自http://www.cnblogs.com/zhenyulu/articles/36061.html

    Liskov Substitution Principle(里氏代换原则):子类型(subtype)必须能够替换它们的基类型。

     

    白马、黑马
     

    反过来的代换不成立
    《墨子·小取》说:"娣,美人也,爱娣,非爱美人也……"娣便是妹妹,哥哥喜爱妹妹,是因为两人是兄妹关系,而不是因为妹妹是个美人。因此,喜爱妹妹不等同于喜爱美人。用面向对象语言描述,美人是基类,妹妹是美人的子类。哥哥作为一个有"喜爱()"方法,接受妹妹作为参数。那么,这个"喜爱()"方法一般不能接受美人的实例。

     

     

    下边那个长方形正方形的例子我就不转了,大家可以到上边那个博客地址中了解。

    2、合成/聚合复用原则(CARP)(以下转自http://www.cnblogs.com/zhenyulu/articles/36068.html
    合成/聚合复用原则(Composite/Aggregate Reuse Principle或CARP)经常又叫做合成复用原则(Composite Reuse Principle或CRP),就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新对象通过向这些对象的委派达到复用已有功能的目的。

    简而言之,要尽量使用合成/聚合,尽量不要使用继承。

    o Design to interfaces.
    o Favor composition over inheritance.
    o Find what varies and encapsulate it.
    (摘自:Design Patterns Explained)

    区分"Has-A"与"Is-A"

    "Is-A"是严格的分类学意义上定义,意思是一个类是另一个类的"一种"。而"Has-A"则不同,它表示某一个角色具有某一项责任。

    导致错误的使用继承而不是合成/聚合的一个常见的原因是错误的把"Has-A"当作"Is-A"。

    例如:
     

    实际上,雇员、经理、学生描述的是一种角色,比如一个人是"经理"必然是"雇员",另外一个人可能是"学生雇员",在上面的设计中,一个人无法同时拥有多个角色,是"雇员"就不能再是"学生"了,这显然是不合理的。

    错误源于把"角色"的等级结构与"人"的等级结构混淆起来,误把"Has-A"当作"Is-A"。解决办法:

     

    总结:

    根据我们前面讲的内容我们可以发现继承的缺点远远多于优点,尽管继承在学习OOP的过程中得到了大量的强调,但并不意味着应该尽可能地到处使用它。相反,使用它时要特别慎重。只有在清楚知道继承在所有方法中最有效的前提下,才可考虑它。 继承最大的优点就是扩展简单,但大多数缺点都很致命,但是因为这个扩展简单的优点太明显了,很多人并不深入思考,所以造成了太多问题,希望这篇文章能引发你一些思考。

     

    参考:

    http://blog.csdn.net/Cpp_Java_Man/archive/2006/05/02/705279.aspx

    http://blog.csdn.net/zjliu1984/archive/2009/06/26/4299657.aspx

    http://www.cnblogs.com/zhenyulu/category/6930.html

    http://www.cnblogs.com/bluedy1229/archive/2008/11/19/1286692.html


    作者:Lance
    出处:http://www.cnblogs.com/nuaalfm/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    unity3d动态加载资源
    u3D大场景的优化
    C#代码规范
    游戏模型规范
    vue prop不同数据类型(数组,对象..)设置默认值
    vue-router实现页面的整体跳转
    Vue实现组件props双向绑定解决方案
    电脑连接并调试手机浏览器的网页
    对象里面的属性有值但是打印出来是空的,获取不到
    vue-cli sass安装
  • 原文地址:https://www.cnblogs.com/nuaalfm/p/1718453.html
Copyright © 2011-2022 走看看