书接上回,本篇继续讲一下设计模式六大原则(有些书认为是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;
}
}
这里也是用继承与组合操作方式,可以喵喵。
总结
存在明显的父子关系 类时候建议使用继承,其他情况使用组合方式。