#1024程序员节|征文#
4.1 适配器模式
- 结构型模式(Structural Pattern)的主要目的就是将不同的类和对象组合在一起,形成更大或者更复杂的结构体。
- 结构性模式的分类:
类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关。
对象结构型模式关心类与对象的组合,通过关联关系在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法。根据合成复用原则,在系统中尽量使用关联关系替代继承关系,因此大部分结构型模式都是对象结构型模式。
4.1.1 适配器模式的定义
1.动机:适配器可以使由于接口不兼容而不能交互的类可以一起工作,这就是适配器模式的模式动机。
2.定义:将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器
4.1.2 适配器模式的分析与实现
在适配器模式中可以定义一个包装类,包装不兼容接口的对象,这个包装类指的就是适配器(Adaptor),它所包装的对象就是适配者(Adaptee),即被适配的类。适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说,当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。
类适配器:
java">public interface Target {public void request();
}
public class Adaptee {public void specialRequest() {System.out.println("特殊的请求方式");}
}
public class Adapter extends Adaptee implements Target{@Overridepublic void request() {super.specialRequest();}
}
对象适配器:
java">public interface Target {public void request();
}
public class Adaptee {public void specialRequest() {System.out.println("特殊的请求方式");}
}
public class Adapter implements Target {private Adaptee adaptee;public void setAdaptee(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {adaptee.specialRequest();}
}
-
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。
-
适配者类无法继承时,只能用对象适配器。
4.1.3 适配器模式的案例
现需要设计一个可以模拟各种动物行为的机器人,在机器人中定义了一系列方法,如机器人叫喊方法cry()、机器人移动方法move()等。如果希望在不修改已有代码的基础上使得机器人能够像狗一样叫,像狗一样跑,使用适配器模式进行系统设计。
java">public interface Robot {public void cry();public void move();
}
public class Dog {public void cry() {System.out.println("汪汪叫");}public void move() {System.out.println("快快跑");}
}
public class DogAdapter extends Dog implements Robot{public void cry(){super.cry();}public void move(){super.move();}
}
4.1.4 适配器模式的优缺点
优点 | 缺点 |
---|---|
1.将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构 | |
2.增加了类的透明性和复用性,提高了适配者的复用性 | |
3.灵活性和扩展性非常好,更换(增加)适配器,符合开闭原则 | |
4.类适配器模式:由于继承关系,置换一些适配者的方法很方便 | 不能适配多个适配类,每个目标类必须为接口,对于适配类为finial修饰的不可使用 |
5.对象适配器模式:可以把多个不同的适配者适配到同一个目标,还可以适配一个适配者的子类 | 在适配器中置换适配者类的某些方法比较麻烦 |
4.1.5 适配器模式的适用场景
-
系统需要使用一些现有的类,而这些类的接口不符合系统的需要,甚至没有这些类的源代码
-
创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作
4.1.6 双向适配器类
java">public class Adapter implements Target,Adaptee {private Target target;private Adaptee adaptee;public Adapter(Target target) {this.target = target; } public Adapter(Adaptee adaptee) {this.adaptee = adaptee; } public void request() {adaptee.specificRequest(); } public void specificRequest() {target.request(); }
}
4.2 桥接模式
4.2.1 桥接模式的定义
1.动机:如果系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使得两者可以独立扩展。桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多重继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同时有效地控制了系统中类的个数。
2.定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
4.2.2 桥接模式的分析与实现
-
桥接模式中体现开闭原则、合成复用原则、里氏代换原则、依赖倒转原则等。
-
脱耦:桥接模式将抽象化和实现化之间的耦合解开,或者说是将强关联(继承)改换成弱关联,将两个角色之间的继承关系改为关联关系,使得两者可以独立变化。
java">public interface Implementor {public void implOperation(); } public class ConcreteImplementorA implements Implementor{@Overridepublic void implOperation() {System.out.println("一个实体的维度1的特征A");} } public class ConcreteImplementorB implements Implementor{@Overridepublic void implOperation() {System.out.println("一个实体的维度1的特征B");} }public abstract class Abstracter {protected Implementor implementor;public void setImplementor(Implementor implementor) {this.implementor = implementor;}public abstract void operation(); } public class RefinedAbstracter extends Abstracter{@Overridepublic void operation() {System.out.println("一个实体维度2的特征");implementor.implOperation();} }
4.2.3 桥接模式的案例
java">public interface VideoFile {public void decode(String osType, String fileName);
}
public class MPEGFile implements VideoFile{@Overridepublic void decode(String osType, String fileName) {System.out.println("位于" + osType + "操作系统的" + fileName + ".MPEG文件进行播放");}
}
public class WMVFile implements VideoFile{@Overridepublic void decode(String osType, String fileName) {System.out.println("位于" + osType + "操作系统的" + fileName + ".WMV文件进行播放");}
}
java">public abstract class OSVersion {protected VideoFile videoFile;public void setVideoFile(VideoFile videoFile) {this.videoFile = videoFile;}public abstract void play(String fileName);
}
public class LinuxVersion extends OSVersion{@Overridepublic void play(String fileName) {videoFile.decode("Linux", fileName);}
}
public class WindowsVersion extends OSVersion{@Overridepublic void play(String fileName) {videoFile.decode("Windows", fileName);}
}
java">public class Main {public static void main(String[] args) {WMVFile wmvFile = new WMVFile();WindowsVersion windowsVersion = new WindowsVersion();windowsVersion.setVideoFile(wmvFile);windowsVersion.play("刺客五六七");MPEGFile mpegFile = new MPEGFile();LinuxVersion linuxVersion = new LinuxVersion();linuxVersion.setVideoFile(mpegFile);linuxVersion.play("喜羊羊与灰太狼");}
}
4.2.4 桥接模式的优缺点
优点 | 缺点 |
---|---|
1.分离抽象接口和实现部分 | 1.不容易正确识别俩个变化的维度 |
2.取代多继承,减少子类个数 | 2.关联关系在抽象层,设计难度大 |
3.扩展性,俩个维度均可扩展 |
4.2.5 桥接模式的适用场景
-
需要在抽象化和具体化之间增加更多的灵活性,扩展性,避免在两个层次之间建立静态的继承关系
-
一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立地进行扩展
-
不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统
4.3 组合模式
4.3.1 组合模式的定义
1.动机:描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象。
2.定义:组合多个对象形成树形结构以表示“部分-整体”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
4.3.2 组合模式的分析与实现
- 组合模式用面向对象的方式来处理树形结构,它为叶子构件和容器构件提供了一个公共的抽象构件类,客户端可以针对该抽象类进行处理,而无需关心所操作的是哪种类型的对象。
- 使用聚合关系使得容器既可以添加叶子节点也可以添加容器
java">public abstract class Component {public abstract void add(Component component);public abstract void remove(Component component);public abstract Component getChild(int i);public abstract void method();
}public class Composite extends Component{private ArrayList<Component> list = new ArrayList<>();@Overridepublic void add(Component component) {list.add(component);}@Overridepublic void remove(Component component) {list.remove(component);}@Overridepublic Component getChild(int i) {return list.get(i);}@Overridepublic void method() {for (Component component : list) {component.method();}}
}public class Leaf extends Component{@Overridepublic void add(Component component) {throw new RuntimeException("叶子节点无法添加元素");}@Overridepublic void remove(Component component) {throw new RuntimeException("叶子节点无法删除元素");}@Overridepublic Component getChild(int i) {throw new RuntimeException("叶子节点无法获取元素");}@Overridepublic void method() {}
}
4.3.3 组合模式的案例
在操作系统中,一个文件夹中可能存放着图像文件,视频文件,文本文件,也可能存放其他的文件夹,而对不同类型的文件进行的浏览操作也不一样。
java">public abstract class AbstractFile {protected String fileName;public abstract void display();public abstract void add(AbstractFile file);public abstract void remove(AbstractFile file);
}
java">public class ImageFile extends AbstractFile{public ImageFile(String fileName) {this.fileName = fileName;}@Overridepublic void display() {System.out.println("正在打开" + this.fileName + ".png");}@Overridepublic void add(AbstractFile file) {throw new RuntimeException("无法添加文件");}@Overridepublic void remove(AbstractFile file) {throw new RuntimeException("无法删除文件");}
}
public class TextFile extends AbstractFile{public TextFile(String fileName) {this.fileName = fileName;}@Overridepublic void display() {System.out.println("正在打开" + this.fileName + ".txt");}@Overridepublic void add(AbstractFile file) {throw new RuntimeException("无法添加文件");}@Overridepublic void remove(AbstractFile file) {throw new RuntimeException("无法删除文件");}
}
java">public class Folder extends AbstractFile{private ArrayList<AbstractFile> files;public Folder(String fileName) {files = new ArrayList<>();this.fileName = fileName;}@Overridepublic void display() {System.out.println("打开" + this.fileName + "文件夹");for (AbstractFile file : files) {file.display();}}@Overridepublic void add(AbstractFile file) {this.files.add(file);}@Overridepublic void remove(AbstractFile file) {this.files.remove(file);}
}
4.3.4 组合模式的优缺点
优点 | 缺点 |
---|---|
1.可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次 | 1.在增加新构件时很难对容器中的构件类型进行限制 |
2.客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构 | 2.不是所有方法在叶子节点中都可以使用 |
3.增加新的容器构件和叶子构件都很方便,符合开闭原则 |
4.3.5 组合模式的适用场景
-
在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们
-
在一个使用面向对象语言开发的系统中需要处理一个树形结构
-
在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型
4.3.6 组合模式的类型
- 透明组合模式
定义:抽象构件中声明了所有用于管理成员对象的方法,包括add()、remove(),以及getChild()等方法。
优点:确保所有的构件类都有相同的接口,客户端一致对待所有对象。
缺点:不够安全,因为叶子对象和容器对象在本质上是有区别的。
- 安全组合模式
定义:抽象构件Component中没有声明任何用于管理成员对象的方法,在Composite类中声明与实现这些方法
优点:优点是安全,对于叶子对象,客户端不可能调用到这些方法
缺点:不够透明,客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件