浅谈设计模式-合成复用原则

书接上回,本篇继续讲一下设计模式六大原则(有些书认为是7大原则)

原则定义

合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),其定义如下:

尽量使用对象组合,而不是继承来达到复用的目的。

这个原则算是最好理解的原则了,就研究代码的复用问题,一般来讲,如果代码重复2遍之后,我们需要考虑代码复用问题,代码复用方式大体有以下几种:

1>抽取工具类,工具方法,重复使用

public class XxxUtil{
    public static void doXxx(){
        //抽取的工具方法
    }
}
使用时:
XxxUtil.doXxx();

2>抽取父类,使用继承方式达到复用效果

public class Parent{
    public  void doXxx(){
        //抽取的公用方法
    }
}

3>抽取一个实例类,使用关联(组合)的方式

public class Xxx{
    public  void doXxx(){
        //抽取的公用方法
    }
}

使用时:
new Xxx().doXxxx();

其实1>3>可以理解为同一个,一个是类行为,另外一个是实例行为。

合成复用原则原则要求尽量使用组合方式实现(第3种方式),主类与抽取出来的实例类以组合的方式组织代码,主类委派调用实例类调用实例方法达到功能复用。比如:

使用继承方式实现复用:

//父类
public class B{
    public void doWork(){
        System.out.println("这是一个代码块");
    }
}
public class A extends B{
}
使用:
new A().doWork();

使用继承方式进行代码复用

优点:

1>实现简单,子类继承父类即可

2>拓展方便,重写父类方法即可

缺点:

1>继承复用破坏封装的完整性,因为继承,子类完全知道父类实现细节。

2>如果父类发生变动,整个继承体系都可能需要进行被动调整。

3>子类从父类继承而来的功能是静态的,不能动态改动,没有足够的灵活性。

使用组合方式:

//抽取的实例类
public class B{
    public void doWork(){
        System.out.println("这是一个代码块");
    }
}
public class A{
    @Setter
    private B b; //通过set注入,或者直接new也行
	public void doWork(){
        b.doWork();
    } 
}
使用:
new A().doWork();

使用组合方式进行代码复用

优点

1>类与类间关联少(依赖少)。

2>没有破坏类的封装,上例中的A类不需要知道B类的doWork的实现

3>可以根据需要对B类对象进行切换,动态实现功能切换(多态),较为灵活

缺点:

需要管理有较多的对象

案例

需求:服务层打印日志(分:控制台日志跟文件日志)

//日志打印父类,制定规范
public abstract class AbstractLogPrint {
    public abstract  void log(String log);
}
//子类:控制台日志打印类
public class ConsoleLogPrint extends  AbstractLogPrint {
    public void log(String log) {
        System.out.println("控制台上打印......." + log);
    }
}
//子类:文件日志打印类
public class FileLogPrint extends  AbstractLogPrint {
    public void log(String log) {
        System.out.println("日志文件中打印......." + log);
    }
}
//服务层类,业务操作逻辑前后打印日志
public class ServiceImpl {
    private AbstractLogPrint print;
    public ServiceImpl(AbstractLogPrint print){
        this.print = print;
    }
    public void doWork(){
        print.log("doWork操作前......");
        //do something.....
        print.log("doWork操作后......");
    }
}
public class App {
    public static void main(String[] args) {
        FileLogPrint fileLogPrint = new FileLogPrint();
        ConsoleLogPrint consoleLogPrint = new ConsoleLogPrint();

        ServiceImpl service1 = new ServiceImpl(fileLogPrint);
        service1.doWork();
        System.out.println("-----------华丽分割线-----------------");
        service1 = new ServiceImpl(consoleLogPrint);
        service1.doWork();

    }
}

结果:

日志文件中打印.......doWork操作前......
日志文件中打印.......doWork操作后......
-----------华丽分割线-----------------
控制台上打印.......doWork操作前......
控制台上打印.......doWork操作后......

解析:

例子中使用AbstractLogPrint类指定日志打印规则,FileLogPrint,ConsoleLogPrint子类作为规则具体实践方,ServiceImpl类,使用组合的方法,引入AbstractLogPrint具体实践,实现动态切换不同的日志打印策略。

合成复用原则,要求在明显父子关系时采用继承,其他情况建议使用组合方法。案例就表达这个意思。

运用

看回到jdk的IO缓存字符流:BufferedReader

BufferedReader 缓存字符流,对字符串流有缓存读的功能。期底层原理是对Reader方法再封装,加上额外的缓存处理。

public class BufferedReader extends Reader {
    private Reader in;
    public BufferedReader(Reader in) {
        this(in, defaultCharBufferSize);
    }
    public BufferedReader(Reader in, int sz) {
        super(in);
        if (sz <= 0)
            throw new IllegalArgumentException("Buffer size <= 0");
        this.in = in;  //赋值
        cb = new char[sz];
        nextChar = nChars = 0;
    }
    //.....
}

看BufferedReader 源码, 继承Reader父类,满足字符流读操作规范。

核心构造器传入一个新的Reader对象,赋值给成员变量in,后续的缓存读操作,直接使用in对象操作,同时额外加上缓存处理。

private void fill() throws IOException {
    //额外的缓存操作,这里省略的....
    int n;
    do {
        //in 对象的读
        n = in.read(cb, dst, cb.length - dst);
    } while (n == 0);
    if (n > 0) {
        nChars = dst + n;
        nextChar = dst;
    }
}

这里也是用继承与组合操作方式,可以喵喵。

总结

存在明显的父子关系 类时候建议使用继承,其他情况使用组合方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浪飞yes

我对钱没兴趣~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值