• 面向对象编程原则(08)——合成复用原则



    版权声明

    • 本文原创作者:谷哥的小弟
    • 作者博客地址:http://blog.csdn.net/lfdfhl

    参考资料

    1. 《大话设计模式》 作者:程杰
    2. 《Java设计模式》 作者:刘伟
    3. 《图解设计模式》 作者:结城浩
    4. 《重学Java设计模式》 作者:付政委
    5. 《Head First设计模式》作者:埃里克·弗里曼

    合成复用原则概述

    合成复用原则又称为组合/聚合复用原则,即Composition/Aggregate Reuse Principle,CARP。

    该原则定义如下:

    优先使用对象组合而不是通过继承来达到复用的目的。

    Favor composition of objects over inheritance as a reuse mechanism

    合成复用原则指的是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用功能的目的。简而言之,在复用时要尽量使用组合/聚合关系(关联关系),而尽量少使用继承。

    在面向对象设计中可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承。但首先应该考虑使用组合/聚合,组合/聚合可以使系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少;其次才考虑继承。在使用继承时需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。

    通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,由于基类的某些内部细节对子类来说是可见的,所以这种复用又称“白箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变。

    由于组合或聚合关系可以将已有的对象(也可称为成员对象)纳入到新对象中,使之成为新对象的一部分。因此新对象可以调用已有对象的功能,这样做可以使成员对象的内部实现细节对于新对象不可见,所以这种复用又称为“黑箱”复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,可以在新对象中根据实际需要有选择性地调用成员对象的操作。

    一般而言,如果两个类之间是“Has-A”的关系应使用组合或聚合,如果是“Is-A”的关系可以使用继承。“Is-A”是严格的分类学意义上的定义,意思是一个类是另一个类的“一种”;而“Has-A”则不同,它表示某一个角色具有某一项责任。

    合成复用原则案例1

    在此,以案例形式介绍合成复用原则。

    版本1

    在项目技术选型中采用MySQL数据库存储数据,并利用MySQL数据库连接对用户进行添加操作。

    在这里插入图片描述

    DBConnection

    package com.carp01;
    /**
     * 原创作者:谷哥的小弟
     * 博客地址:http://blog.csdn.net/lfdfhl
     */
    public class DBConnection {
        public String getConnection() {
            return "获取MySQL数据库连接";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    UserDao

    package com.carp01;
    /**
     * 原创作者:谷哥的小弟
     * 博客地址:http://blog.csdn.net/lfdfhl
     */
    public class UserDao extends DBConnection {
        public void addUser() {
            String connection = super.getConnection();
            System.out.println(connection);
            System.out.println("执行数据库操作");
            // ......省略后续操作......
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    Test

    package com.carp01;
    /**
     * 原创作者:谷哥的小弟
     * 博客地址:http://blog.csdn.net/lfdfhl
     */
    public class Test {
    
    	public static void main(String[] args) {
    		  UserDao userDao = new UserDao();
    	      userDao.addUser();
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    小结

    在该示例中UserDao继承自DBConnection,getConnection( )方法只能返回MySQL数据库的连接。但是,随着业务的扩展功能增加了;又新增了Oracle数据库。此时,原本的UserDao就不能满足需求了。虽然,我们可在DBConnection中再新增一个方法用于获取 Oracle 数据库的连接;但是这样显然违反了开闭原则。

    版本2

    我们利用合成复用原则对示例代码进行重构。将数据库连接类设计为抽象类,其内部包含一个获取数据库连接的抽象方法;至于获取哪种数据库的连接则交于具体的子类去实现。另外,我们在UserDao中持有DBConnection类型的引用。

    在这里插入图片描述

    DBConnection

    package com.carp02;
    /**
     * 原创作者:谷哥的小弟
     * 博客地址:http://blog.csdn.net/lfdfhl
     */
    public abstract class DBConnection {
        public abstract String getConnection();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    MySQLConnection

    package com.carp02;
    /**
     * 原创作者:谷哥的小弟
     * 博客地址:http://blog.csdn.net/lfdfhl
     */
    public class MySQLConnection extends DBConnection {
        @Override
        public String getConnection() {
            return "获取MySQL数据库连接";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    OracleConnection

    package com.carp02;
    /**
     * 原创作者:谷哥的小弟
     * 博客地址:http://blog.csdn.net/lfdfhl
     */
    public class OracleConnection extends DBConnection {
        @Override
        public String getConnection() {
            return "获取Oracle数据库连接";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    UserDao

    package com.carp02;
    /**
     * 原创作者:谷哥的小弟
     * 博客地址:http://blog.csdn.net/lfdfhl
     */
    public class UserDao {
    	// 持有DBConnection类型的引用
        private DBConnection connection;
    
        public void setConnection(DBConnection connection) {
            this.connection = connection;
        }
    
        public void addUser() {
            System.out.println(connection);
            System.out.println("执行数据库操作");
            // ......省略后续操作......
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    Test

    package com.carp02;
    /**
     * 原创作者:谷哥的小弟
     * 博客地址:http://blog.csdn.net/lfdfhl
     */
    public class Test {
    
    	public static void main(String[] args) {
    		UserDao userDao = new UserDao();
    		
    		MySQLConnection mySQLConnection = new MySQLConnection();
    		userDao.setConnection(mySQLConnection);
    		userDao.addUser();
    
    		OracleConnection oracleConnection = new OracleConnection();
    		userDao.setConnection(oracleConnection);
    		userDao.addUser();
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    合成复用原则案例2

    汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多;图示如下:

    在这里插入图片描述

    从上面类图我们可以看到:使用继承复用产生了很多子类,如果现在又有新的动力源或者新的颜色的话,就需要再定义新的类。在此,我们试着将继承复用改为聚合复用;图示如下:

    在这里插入图片描述

  • 相关阅读:
    网络安全(黑客)自学
    解读最早的草图-图像翻译工作SketchyGAN
    【TEC100TAI-KIT】青翼科技基于复微青龙JFMQL100TAI的全国产化智能异构计算平台
    Java SPI机制分析
    在Visual Studio 2017上配置Glut
    Gradio 最快创建Web 界面部署到服务器并演示机器学习模型,本文提供教学案例以及部署方法,避免使用繁琐的django
    redis实战篇03
    登录模块开发
    Java Web——TomcatWeb服务器
    c++ 之 socket udp与tcp client server实现
  • 原文地址:https://blog.csdn.net/lfdfhl/article/details/126679560