原则4-聚组复用原则

前一阵子参与一个项目,对某产品第一个版本的代码进行了重构,原因是项目工期紧,要求第一个版本以最短的时间拿出来,导致产品在部分需求未确定的情况下草草开始,匆匆结束。项目要求有大概五六个子系统,每个子系统间必须使用socket通讯,考虑到数据安全性和具体协议的不确定性,商议新版代码通讯暂时使用tcp协议进行交互,伪代码如下:

 

public class Connector {

private String ip;

private int port;

public boolean connect(String strIp,int iPort)

{

System.out.printf("try to connect tcp ip:%s ,port:%d\n", strIp,iPort);

this.ip = strIp;

this.port = iPort;

return true;

}

public boolean sendData(String strData)

{

System.out.printf("try to send tcp data:%s \n", strData);

return true;

}

public String recvData()

{

System.out.printf("try to recv tcp data\n");

return "hello word";

}

}

现在我想使用这个类去接收/解析/发送数据,所以我写了一个parse类,之前在学习适配器模式的时候讲过,一般适配器适配的方法有两种,一种是在被适配的类的基础上派生,一种是在适配器类中引用被适配类的实例。这也是parse类使用Connector的两种方法,抛去引用实例的方法不说,咱们就谈谈使用第一种方法的弊端。

 

继承方式的parse代码如下:

public class Parser extends Connector{

public Parser()

{

connect("192.168.23.1", 8080);

}

public boolean dealCmd(String strData)

{

sendData(strData);

recvData();

//……………………

return true;

}

}

 

这样确实实现了目前的代码,但是多变的需求提出要支持UDP协议,我们怎么办?将Parser 的基类从Connector换成UdpConnector?那要再用tcp怎么办?并且无论修改Parser 还是修改Connector都不符合开闭原则,那么问题出在哪里了呢?

 

我们先看看什么叫复用,所谓的复用就是将某个功能进行封装, 使得所有要使用此功能的代码上下文都可以使用此一套代码。这样此功能的实现细节再发生改变的时候只需要修改此一处代码。代码复用的方法有“白箱复用”和“黑箱复用”两种:

白箱复用:B复用A的功能,并且B可以了解A的内部细节,使用技术是公开继承

黑箱复用:B复用A的功能,但B无法看到A的内部细节,使用技术是关联/聚合/组合

 

从我们的代码来看,Parser 派生自 Connector ,也就是说Parser 对Connector 通讯功能的复用是白箱复用,因为继承使得Parser 对 Connector 的细节必须了解,这影响了封装。如果基类发生了改变,那么封装类也必须跟着改变,并且继承来的功能是静态的,不能在运行时灵活的变化,这都是弊端。

这就衍生出了咱们将要说的聚组复用原则。

 

聚组复用原则:对已有功能的复用要尽量使用组合/聚合,尽量不要使用类继承。

 

也就是说,继承不是用的越多对产品扩展越有利,在功能复用的时候,要考虑好两个类之间的关系,确认B是A的一种,才能使用继承。

 

在上面的例子中,我们需要理清楚parse类和Connector类之间的关系,parse类虽然要复用Connector类的功能,但parse并不是Connector的一种,就如同屠夫庖丁解牛,屠夫用刀切肉,你能说屠夫就是一种刀吗?不能,刀是水果刀、菜刀、切肉刀、金丝大环刀,而屠夫只是再使用刀,拥有这个刀。parse和Connector的关系这在oop中属于从属关系,即has-a,我们来看下面的名词:

Is-a:

是a:A Is B:A是B(继承关系,继承)。

 

has-a:

有a:A has B:A有B(从属关系,聚合)。

 

like-a:

像a:A like B:A像B(组合关系,接口)。

 

关于Is-a、has-a、like-a的使用场景:

如果A,B是Is-a关系,那么应该使用继承,例:玻璃杯、塑料杯都是杯子。

如果A,B是has-a关系,那么应该是用聚合,例:汽车由发动机,底盘,车身,电气设备等组成,那么应该把发动机,底盘这些类聚合成汽车。

如果A,B是like-a关系,那么应该使用组合,例:空调继承于制冷机,但它同时有加热功能,那么你应该把让空调继承制冷机类,并实现加热接口。

 

综上,我们来看看我们的程序需要如何修改,我们先确认parse是Commector是聚合关系,又有依赖倒置原则,我们可以对Connector进行抽象,让parse关联此抽象,UML如下:

代码如下:

public abstract class IConnector {

protected String ip;

protected int port;

abstract boolean connect(String strIp,int iPort);

abstract boolean sendData(String strData);

abstract String recvData();

}

 

public class TcpConnector extends IConnector{

 

@Override

boolean connect(String strIp, int iPort) {

// TODO Auto-generated method stub

System.out.printf("try to connect tcp ip:%s ,port:%d\n", strIp,iPort);

super.ip = strIp;

super.port = iPort;

return true;

}

 

@Override

boolean sendData(String strData) {

// TODO Auto-generated method stub

System.out.printf("try to send tcp data:%s \n", strData);

return true;

}

 

@Override

String recvData() {

// TODO Auto-generated method stub

System.out.printf("try to recv tcp data\n");

return "hello word";

}

 

}

 

public class Parser{

private IConnector conn;

public Parser(){}

public boolean setConnecotr(IConnector con)

{

conn = con;

conn.connect("192.168.1.111",234);

return true;

}

public boolean dealCmd(String strData)

{

this.conn.sendData(strData);

this.conn.recvData();

//……………………

return true;

}

}

 

public class Client {

public static void main(String[] args) {

Parser pp = new Parser();

IConnector conn = new TcpConnector();

pp.setConnecotr(conn);

pp.dealCmd("hello service!");

}

}

 

如果需要使用udp进行数据传输,那么直接实现一个基于Iconnector的UdpConnector类,同时修改Client一行代码即可。这就不会影响parse,符合开闭原则。

 

下面补充说一下聚组复用/继承复用的优缺点:

 

合成/聚合复用:

(1).优点:

        新对象存取成分对象的唯一方法是通过成分对象的接口; 这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的;这种复用支持包装;这种复用所需的依赖较少; 每一个新的类可以将焦点集中在一个任务上; 这种复用可以在运行时动态进行,新对象可以使用合成/聚合关系将新的责任委派到合适的对象。

(2).缺点:

         通过这种方式复用建造的系统会有较多的对象需要管理。

 

继承复用:

(1).优点:

        新的实现较为容易,因为基类的大部分功能可以通过继承关系自动进入派生类;修改或扩展继承而来的实现较为容易。

(2).缺点:

        继承复用破坏包装,因为继承将基类的实现细节暴露给派生类,这种复用也称为白箱复用; 如果基类的实现发生改变,那么派生类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,不够灵活。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值