“单一职责”模式:
使用场景
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
典型模式
Decorator(装饰模式)
Bridge(桥接模式)
Decorator 装饰模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项:可代替继承。
使用场景
在某些情况下我们可能会“过度地使用继承来扩展对象的功能”由于继承为类型引入的静态特质,使得这种扩展方法缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。
模式定义
动态(组合)地给一个对象增加一些额外的职责。就增加功能而言,Decorator模式比生成子类(继承)更多灵活(消除重复代码&减少子类个数)。
类图结构
代码举例
//业务操作
abstract class Stream {
public abstract char read(int number);
public abstract void seek(int position);
public abstract void write(char data);
}
//主体类
class FileStream extends Stream {
//读文件流
@Override
public char read(int number){
//***********
}
//定位文件流
@Override
public void seek(int position){
//***********
}
//写文件流
@Override
public void write(char data){
//***********
}
}
class NetworkStream extends Stream {
//读文件流
@Override
public char read(int number){
//###########
}
//定位文件流
@Override
public void seek(int position){
//###########
}
//写文件流
@Override
public void write(char data){
//###########
}
}
class MemoryStream extends Stream {
//读文件流
@Override
public char read(int number){
//@@@@@@@@@@@
}
//定位文件流
@Override
public void seek(int position){
//@@@@@@@@@@@
}
//写文件流
@Override
public void write(char data){
//@@@@@@@@@@@
}
}
//扩展操作
class CryptoFileStream extends FileStream {
@Override
public char read(int number) {
//额外的加密操作...
//读文件流
super.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位文件流
super.seek(position);
//额外的加密操作...
}
//写内存流
@Override
public void write(char data) {
//额外的加密操作...
//写文件流
super.write(data);
//额外的加密操作...
}
}
class CryptoNetworkStream extends NetworkStream {
@Override
public char read(int number) {
//额外的加密操作...
//读网络流
super.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位网络流
super.seek(position);
//额外的加密操作...
}
@Override
public void write(char data) {
//额外的加密操作...
//写网络流
super.write(data);
//额外的加密操作...
}
}
class CryptoMemoryStream extends MemoryStream {
@Override
public char read(int number) {
//额外的加密操作...
//读内存流
super.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位内存流
super.seek(position);
//额外的加密操作...
}
//写内存流
@Override
public void write(char data) {
//额外的加密操作...
//写内存流
super.write(data);
//额外的加密操作...
}
}
class BufferedFileStream extends FileStream {
}
class BufferedNetworkStream extends NetworkStream {
}
class BufferedMemoryStream extends MemoryStream {
}
class CryptoBufferedFileStream extends FileStream {
@Override
public char read(int number) {
//额外的加密操作...
//额外的缓冲操作...
//读文件流
super.read(number);
//额外的加密操作...
//额外的缓冲操作...
}
@Override
public void seek(int position) {
//额外的加密操作...
//额外的缓冲操作...
//定位文件流
super.seek(position);
//额外的加密操作...
//额外的缓冲操作...
}
@Override
public void write(char data) {
//额外的加密操作...
//额外的缓冲操作...
//写文件流
super.write(data);
//额外的加密操作...
//额外的缓冲操作...
}
}
class Client {
public static void main(String args[]) {
//编译时装配 编译的时候 类已经存在
CryptoFileStream fs1 = new CryptoFileStream();
BufferedFileStream fs2 = new BufferedFileStream();
CryptoBufferedFileStream fs3 = new CryptoBufferedFileStream();
}
}
代码中类的设计
代码设计存在缺陷,代码中的类如果使用公式表示,类的数量级是(1+n+(n*m!))
1是Stream。n是FileStream,NetWorkStream,MemoryStream。(n*m!)是各个....Stream的CryptoFileStream等类。
代码中 在各个类的seek()和read()和write()中的 额外的加密操作 在各个类中都是相同的操作,都是重复的代码。并且在方法结构上也是十分相似的。
子类的急剧膨胀,同时充斥着重复代码,应该使用单一职责模式思想来重构代码。过度使用继承。
super,read(number)是静态特质。不是多态调用,是静态调用,需要找到父类的read(),是编译时绑定关系
继承转组合 改良代码
//业务操作
abstract class Stream {
public abstract char read(int number);
public abstract void seek(int position);
public abstract void write(char data);
}
//主体类
class FileStream extends Stream {
//读文件流
@Override
public char read(int number){
//***********
}
//定位文件流
@Override
public void seek(int position){
//***********
}
//写文件流
@Override
public void write(char data){
//***********
}
}
class NetworkStream extends Stream {
//读文件流
@Override
public char read(int number){
//###########
}
//定位文件流
@Override
public void seek(int position){
//###########
}
//写文件流
@Override
public void write(char data){
//###########
}
}
class MemoryStream extends Stream {
//读文件流
@Override
public char read(int number){
//@@@@@@@@@@@
}
//定位文件流
@Override
public void seek(int position){
//@@@@@@@@@@@
}
//写文件流
@Override
public void write(char data){
//@@@@@@@@@@@
}
}
//继承:遵守接口协议
class CryptoStream extends Stream {
//组合:复用实现 运行时候声明成具体的类 将变化的放到运行时,让运行时支持以后灵活的变化
Stream s;// s=new FileStream(); s=new NetworkStream(); s=new MemoryStream();
//将super改成 s 的时候 FileStream()NetworkStream()MemoryStream() 创建的类代码都是一样的
//除了 s=new FileStream()
public CryptoStream(Stream s)
{
this.s=s;
}
@Override
public char read(int number) {
//额外的加密操作...
//读文件流
s.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位文件流
s.seek(position);
//额外的加密操作...
}
//写内存流
@Override
public void write(char data) {
//额外的加密操作...
//写文件流
s.write(data);
//额外的加密操作...
}
}
class BufferedStream extends Stream {
Stream s;
public BufferedStream(Stream s)
{
this.s=s;
}
}
class Client {
public static void main(String args[]) {
//运行时装配 编译时还不存在 根据需要去new 对象
Stream s1=new CryptoStream( new FileStream());
Stream fs2 = new BufferedStream(new FileStream());
Stream fs3 = new CryptoStream(new BufferedStream(new FileStream()));
}
}
消除了重复性,类的数目大大减少
s.read(number) 是动态调用,因为s 是抽象类Stream的指针。使用抽象类s 调用read()抽象方法,会根据运行时候的类型,比如new FileStream(),就调用FileStream的read()。
对代码再进行优化 几个类都继承同一个父类,并且里面有一样的字段,应该把这个字段往上提。
使用中间基类DecroratorStream
//业务操作
abstract class Stream {
public abstract char read(int number);
public abstract void seek(int position);
public abstract void write(char data);
}
//主体类
class FileStream extends Stream {
//读文件流
@Override
public char read(int number){
//***********
}
//定位文件流
@Override
public void seek(int position){
//***********
}
//写文件流
@Override
public void write(char data){
//***********
}
}
class NetworkStream extends Stream {
//读文件流
@Override
public char read(int number){
//###########
}
//定位文件流
@Override
public void seek(int position){
//###########
}
//写文件流
@Override
public void write(char data){
//###########
}
}
class MemoryStream extends Stream {
//读文件流
@Override
public char read(int number){
//@@@@@@@@@@@
}
//定位文件流
@Override
public void seek(int position){
//@@@@@@@@@@@
}
//写文件流
@Override
public void write(char data){
//@@@@@@@@@@@
}
}
//继承:接口协议
abstract DecroratorStream extends Stream{
//组合:复用实现
protected Stream s;// s=new FileStream(); s=new NetworkStream(); s=new MemoryStream();
protected DecroratorStream(Stream s){
this.s=s;
}
}
class CryptoStream extends DecroratorStream {
public CryptoStream(Stream s)
{
super(s);
}
@Override
public char read(int number) {
//额外的加密操作...
//读文件流
s.read(number);
}
@Override
public void seek(int position) {
//额外的加密操作...
//定位文件流
s.seek(position);
//额外的加密操作...
}
//写内存流
@Override
public void write(char data) {
//额外的加密操作...
//写文件流
s.write(data);
//额外的加密操作...
}
}
class BufferedStream extends DecroratorStream {
Stream s;
public BufferedStream(Stream s)
{
super(s);
}
//....
}
class Client {
public static void main(String args[]) {
//运行时装配
FileStream fs=new FileStream();
Stream s1=new CryptoStream( new FileStream());
Stream s2 = new BufferedStream(new FileStream());
Stream s3 = new CryptoStream(new BufferedStream(new FileStream()));
}
}
改良后的类数目减少 类数量级公式为 1+n+1+m
运行时装配,可以灵活面对多个功能的组合需求。 刚开始的代码,类的数量很多,都是编译时装配。
类图结构中Component对应以上代码的抽象类Stream。ConcreteComponent对应的是FileStream,NetworkStream,MemoryStream。Decorator对应的是DecroratorStream,既继承,又组合。有一个指针指向Component,又继承Component。ConcreteDecoratorA对应的是BufferedStream。ConcreteDecoratorB对应的是CryptoStream。
Component和Decorator是稳定的。ConcreteComponent和ConcreteDecoratorA和ConcreteDecoratorB是变化的。
Android使用装饰模式的体现
查看java io 源码.
InputStream.java相当于上面代码的Stream
FilterInputStream.java是继承InputStream并且又组合InputStream。相当于Decorator
要点总结
- 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
- Decorator类在接口是表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另一个Component类。
- Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
Bridge (桥接模式)
由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个维度的变化。
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
意图:将抽象部分与实现部分分离,使它们都可以独立的变化。
主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。
如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
关键代码:抽象类依赖实现类。
应用实例: 1、猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。 2、墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。
优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
使用场景: 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。 2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。
模式定义
将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。
类图结构
代码举例
假设设计一个消息通信软件
abstract class Messager {
//第一种变化维度
public abstract void login(String username, String password);
public abstract void sendMessage(String message);
public abstract void sendPicture(Image image);
//第二种变化维度
public abstract void playSound();
public abstract void drawShape();
public abstract void writeText();
public abstract void connect();
}
//假设用n来表示平台数量 支持 m个功能版本,m个功能版本在n个平台的排列组合是n*m
//假设有5个平台,有5个版本
//1+n+n*m =1+5+5*5= 31
//PC平台实现
abstract class PCMessagerBase extends Messager {
@Override
public void playSound() {
//**********
}
@Override
public void drawShape() {
//**********
}
@Override
public void writeText() {
//**********
}
@Override
public void connect() {
//**********
}
}
//手机平台实现
abstract class MobileMessagerBase extends Messager {
@Override
public void playSound() {
//==========
}
@Override
public void drawShape() {
//==========
}
@Override
public void writeText() {
//==========
}
@Override
public void connect() {
//==========
}
}
//PC普通版本
class PCMessagerLite extends PCMessagerBase {
@Override
public void login(String username, String password) {
super.connect();
//........
}
@Override
public void sendMessage(String message) {
super.writeText();
//........
}
@Override
public void sendPicture(Image image) {
super.drawShape();
//........
}
}
//PC商业版本
class PCMessagerPerfect extends PCMessagerBase {
@Override
public void login(String username, String password) {
super.playSound();
//********
super.connect();
//........
}
@Override
public void sendMessage(String message) {
super.playSound();
//********
super.writeText();
//........
}
@Override
public void sendPicture(Image image) {
super.playSound();
//********
super.drawShape();
//........
}
}
//手机普通版本
class MobileMessagerLite extends MobileMessagerBase {
@Override
public void login(String username, String password) {
super.connect();
//........
}
@Override
public void sendMessage(String message) {
super.writeText();
//........
}
@Override
public void sendPicture(Image image) {
super.drawShape();
//........
}
}
//手机商业版本
class MobileMessagerPerfect extends MobileMessagerBase {
@Override
public void login(String username, String password) {
super.playSound();
//********
super.connect();
//........
}
@Override
public void sendMessage(String message) {
super.sendMessage(message);
//********
super.writeText();
//........
}
@Override
public void sendPicture(Image image) {
super.playSound();
//********
super.drawShape();
//........
}
}
class Client {
public static void main(String args[]) {
//编译时装配
Messager m = new MobileMessagerPerfect();
}
}
缺点 PCMessagerBase和MobileMessagerBase,PCMessagerLite和 MobileMessagerLite,PCMessagerPerfect和MobileMessagerPerfect功能结构十分相似,代码重复严重,不一样的地方就是super 调用的地方。
出现了子类再膨胀,代码在重复特征。
对以上代码进行改良
消除重复性用的技术,让编译时相同,把变化的部分以多态的形式给运行时执行。
几个类继承同一个父类,包含同一个字段。应该把此字段提到父类。
abstract class Messager {
protected MessagerImp msgImp;
protected Messager(MessagerImp msgImp){
this.msgImp=msgImp;
}
public abstract void login(String username, String password);
public abstract void sendMessage(String message);
public abstract void sendPicture(Image image);
}
abstract class MessagerImp{
//PCMessagerImp 和MobileMessagerImp 都继承MessagerImp 它们只重写这几个函数
public abstract void playSound();
public abstract void drawShape();
public abstract void writeText();
public abstract void connect();
}
//假设平台数量 n=5,支持功能数量m=5
//1+1+n+m =1+1+5+5= 12
//平台实现
class PCMessagerImp extends MessagerImp {
@Override
public void playSound() {
//**********
}
@Override
public void drawShape() {
//**********
}
@Override
public void writeText() {
//**********
}
@Override
public void connect() {
//**********
}
}
class MobileMessagerImp extends MessagerImp {
@Override
public void playSound() {
//==========
}
@Override
public void drawShape() {
//==========
}
@Override
public void writeText() {
//==========
}
@Override
public void connect() {
//==========
}
}
//业务抽象
class MessagerLite extends Messager {
@Override
public void login(String username, String password) {
msgImp.connect();
//........
}
@Override
public void sendMessage(String message) {
msgImp.writeText();
//........
}
@Override
public void sendPicture(Image image) {
msgImp.drawShape();
//........
}
}
class MessagerPerfect extends Messager {
@Override
public void login(String username, String password) {
msgImp.playSound();
//********
msgImp.connect();
//........
}
@Override
public void sendMessage(String message) {
msgImp.playSound();
//********
msgImp.writeText();
//........
}
@Override
public void sendPicture(Image image) {
msgImp.playSound();
//********
msgImp.drawShape();
//........
}
}
class Client {
public static void main(String args[]) {
//编译时装配
Messager m = new MessagerPerfect( new PCMessagerImp());
}
}
public abstract void login(String username, String password);
public abstract void sendMessage(String message);
public abstract void sendPicture(Image image);
就是抽象部分
login()通过super.connect()来实现的。sendMessage()通过super.writeText()来实现的。sendPicture()通过super.drawShape()来实现的。
public abstract void playSound();
public abstract void drawShape();
public abstract void writeText();
public abstract void connect();
就是平台实现。这些代码是帮助代码实现业务功能抽象的。
改良后的代码已经把抽象部分和平台实现分离开来,在抽象部分实例化平台实现的类,让它们关联。使得抽象部分和平台实现桥接起来,让平台实现独立变化。
在类图结构中Abstraction对应的是Messager类。Implementor对应的是MessagerImp类。RefinedAbstraction对应的是MessagerLite,MessagerPerfect类。ConcreteImplementorA,ConcreteImplementorB对应的是PCMessagerImp和MobileMessagerImp类。
Abstraction有一个指针指向Implementor,即代码中的 protected MessagerImp msgImp;并且在Abstraction的Operation()内部调用Implementor里面的方法。即代码中的 msgImp.connect();
Abstraction和Implementor是稳定的部分。RefinedAbstraction(业务功能的方向变化)和ConcreteImplementorA,ConcreteImplementorB(平台实现方向的变化)是变化部分。
要点总结
- Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,即“子类化”它们。
- Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。
- Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式。