• OOP组合和继续的优缺点


    —— 详解继承与组合的优缺点

     

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

    组合:继承:

    一、基础知识

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

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

  • 相关阅读:
    关于32位操作系统和64位操作系统对InstallShield打包的影响
    NEWS: Symantec宣布Wise Package Studio将终止
    InstallShield 2012新功能试用(2) 调用MsiGetProperty等MSI API发生变化
    Basic INFO 在命令行Build InstallShield安装包工程获得压缩安装包
    NEWS InstallShield 2012 Service Pack 1发布
    Basic INFO InstallShield Basic MSI工程中如何在SetupCompleteSuccess界面中启动Readme
    Basic INFO InstallShield的脚本编辑器中如何显示代码行号
    Basic INFO 关于在InstallShield制作的安装包界面中删除InstallShield文字的厂商回复
    Basic INFO InstallShield工程中如何让产品的快捷方式名称始终与产品名保持一致
    Basic INFO: 创建隐藏文件夹
  • 原文地址:https://www.cnblogs.com/keyyang/p/3941548.html
Copyright © 2020-2023  润新知