结构型模式次要总结了一些类或对象组合在一起的经典构造,这些经典的构造能够解决特定利用场景的问题。
罕用的:代理模式、桥接模式、装璜者模式、适配器模式
不罕用的:门面模式、组合模式、亨元模式
一、代理模式
它在不扭转原始类(或叫被代理类)代码的状况下,通过引入代理类来给原始类附加性能。
1.1 实现形式
-
代理类和原始类实现雷同的接口
public interface IUserController {UserVo login(String telephone, String password); UserVo register(String telephone, String password); } public class UserController implements IUserController { //... 省略其余属性和办法... @Override public UserVo login(String telephone, String password) { //... 省略 login 逻辑... //... 返回 UserVo 数据... } @Override public UserVo register(String telephone, String password) { //... 省略 register 逻辑... //... 返回 UserVo 数据... } } public class UserControllerProxy implements IUserController { private MetricsCollector metricsCollector; private UserController userController; public UserControllerProxy(UserController userController) { this.userController = userController; this.metricsCollector = new MetricsCollector();} @Override public UserVo login(String telephone, String password) {long startTimestamp = System.currentTimeMillis(); // 委托 UserVo userVo = userController.login(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return userVo; } @Override public UserVo register(String telephone, String password) {long startTimestamp = System.currentTimeMillis(); UserVo userVo = userController.register(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return userVo; } } //UserControllerProxy 应用举例 // 因为原始类和代理类实现雷同的接口,是基于接口而非实现编程 // 将 UserController 类对象替换为 UserControllerProxy 类对象,不须要改变太多代码 IUserController userController = new UserControllerProxy(newUserController());
-
继承的形式
public class UserControllerProxy extends UserController { private MetricsCollector metricsCollector; public UserControllerProxy() {this.metricsCollector = new MetricsCollector(); } public UserVo login(String telephone, String password) {long startTimestamp = System.currentTimeMillis(); UserVo userVo = super.login(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return userVo; } public UserVo register(String telephone, String password) {long startTimestamp = System.currentTimeMillis(); UserVo userVo = super.register(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo("register", responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return userVo; } } //UserControllerProxy 应用举例 UserController userController = new UserControllerProxy();
-
动静代理
public class MetricsCollectorProxy { private MetricsCollector metricsCollector; public MetricsCollectorProxy() {this.metricsCollector = new MetricsCollector(); } public Object createProxy(Object proxiedObject) {Class<?>[] interfaces = proxiedObject.getClass().getInterfaces(); DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject); returnProxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler); } private class DynamicProxyHandler implements InvocationHandler { private Object proxiedObject; public DynamicProxyHandler(Object proxiedObject) {this.proxiedObject = proxiedObject;} @Override public Object invoke(Object proxy, Method method, Object[] args) throwsThrowable {long startTimestamp = System.currentTimeMillis(); Object result = method.invoke(proxiedObject, args); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; String apiName = proxiedObject.getClass().getName() + ":" +method.getName(); RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp); metricsCollector.recordRequest(requestInfo); return result; } } } //MetricsCollectorProxy 应用举例 MetricsCollectorProxy proxy = new MetricsCollectorProxy(); IUserController userController = (IUserController) proxy.createProxy(newUserController());
1.2 核心内容
- Subject(形象主题角色):它申明了实在主题和代理主题的独特接口,使得在任何应用实在主题的中央都能够应用代理主题,客户端通常须要针对形象主题角色进行编程。
- Proxy(代理主题角色):代理主题角色外部蕴含了对实在主题的援用,从而能够在任何时候操作实在主题对象。在代理主题角色中提供一个与实在主题角色雷同的接口,以便在任何时候都能够代替实在主题。代理主题角色还能够管制对实在主题的应用,负责在须要的时候创立和删除实在主题对象,并对实在主题对象的应用加以束缚。通常,在代理主题角色中,客户端在调用所援用的实在主题操作之前或之后还须要执行其余操作,而不仅仅是单纯调用实在主题对象中的操作。
- RealSubject(实在主题角色):它定义了代理角色所代表的实在对象,在实在主题角色中实现了实在的业务操作,客户端能够通过代理主题角色间接调用实在主题角色中定义的操作。
1.3 利用场景:
-
业务零碎的非功能性需要开发
监控、统计、鉴权、限流、事务、幂等、日志
-
代理模式在 RPC、缓存中的利用
RPC 框架也能够看作一种代理模式
二、桥接模式
将形象和实现解耦,让它们能够独立变动。
2.1 实现形式
Class.forName("com.mysql.jdbc.Driver");// 加载及注册 JDBC 驱动程序
String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=your_password";
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();String query = "select * from test";
ResultSet rs=stmt.executeQuery(query);
while(rs.next()) {rs.getString(1);
rs.getInt(2);
}
package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
* @throws SQLException if a database error occurs.
*/
public Driver() throws SQLException {// Required for Class.forName().newInstance()}
}
public class DriverManager {private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = newCopyOnWriteArrayList<DriverInfo>();
//...
static {loadInitialDrivers();
println("JDBC DriverManager initialized");
}
//...
public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {if (driver != null) {registeredDrivers.addIfAbsent(new DriverInfo(driver));
} else {throw new NullPointerException();
}
}
public static Connection getConnection(String url, String user, String password) throws SQLException {java.util.Properties info = new java.util.Properties();
if (user != null) {info.put("user", user);
}
if (password != null) {info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
//...
}
2.2 核心内容
- Abstraction(抽象类):用于定义抽象类的接口,它个别是抽象类而不是接口,其中定义了一个 Implementor(实现类接口)类型的对象并能够保护该对象。抽象类与 Implementor 之间具备关联关系,它既能够蕴含形象业务办法,也能够蕴含具体业务办法。
- RefinedAbstraction(裁减抽象类):裁减由 Abstraction 定义的接口,通常状况下它不再是抽象类而是具体类。裁减抽象类实现了在 Abstraction 中申明的形象业务办法,在 RefinedAbstraction 中能够调用在 Implementor 中定义的业务办法。
- Implementor(实现类接口):定义实现类的接口,这个接口不肯定要与 Abstraction 的接口完全一致,事实上这两个接口能够齐全不同。一般而言,Implementor 接口仅提供基本操作,而 Abstraction 定义的接口可能会做更多、更简单的操作。Implementor 接口对这些基本操作进行了申明,而具体实现交给其子类。通过关联关系,在 Abstraction 中不仅领有本人的办法,还能够调用到 Implementor 中定义的办法,应用关联关系来代替继承关系。
- ConcreteImplementor(具体实现类):具体实现 Implementor 接口,在不同的 ConcreteImplementor 中提供基本操作的不同实现。在程序运行时,ConcreteImplementor 对象将替换其父类对象,提供给抽象类具体的业务操作方法。
2.3 实用场景
- 如果一个零碎须要在抽象类和具体类之间减少更多的灵活性,防止在两个档次之间建设动态的继承关系,通过桥接模式能够使它们在形象层建设一个关联关系。
- 形象局部和实现局部能够以继承的形式独立扩大而互不影响,在程序运行时能够动静地将一个抽象类子类的对象和一个实现类子类的对象进行组合,即零碎须要对抽象类角色和实现类角色进行动静耦合。
- 一个类存在两个(或多个)独立变动的维度,且这两个(或多个)维度都须要独立进行扩大。
- 对于那些不心愿应用继承或因为多层继承导致系统类的个数急剧减少的零碎,桥接模式尤为实用。
三、装璜器模式
动静地给一个对象减少一些额定的职责,就减少对象性能来说,装璜模式比生成子类实现更为灵便。装璜模式是一种对象结构型模式。
3.1 实现形式
public abstract class InputStream {
//...
public int read(byte b[]) throws IOException {return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {//...}
public long skip(long n) throws IOException {//...}
public int available() throws IOException {return 0;}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {throw new IOException("mark/reset not supported");
}
public boolean markSupported() {return false;}
}
public class BufferedInputStream extends InputStream {
protected volatile InputStream in;
protected BufferedInputStream(InputStream in) {this.in = in;}
//... 实现基于缓存的读数据接口...
}
public class DataInputStream extends InputStream {
protected volatile InputStream in;
protected DataInputStream(InputStream in) {this.in = in;}
//... 实现读取根本类型数据的接口
}
3.2 核心内容
- Component(形象构件):它是具体构件和形象装璜类的独特父类,申明了在具体构件中实现的业务办法。它的引入能够使客户端以统一的形式解决未被装璜的对象以及装璜之后的对象,实现客户端的通明操作。
- ConcreteComponent(具体构件):它是形象构件类的子类,用于定义具体的构件对象,实现了在形象构件中申明的办法,装璜器能够给它减少额定的职责(办法)。
- Decorator(形象装璜类):它也是形象构件类的子类,用于给具体构件减少职责,然而具体职责在其子类中实现。它保护一个指向形象构件对象的援用,通过该援用能够调用装璜之前构件对象的办法,并通过其子类扩大该办法,以达到装璜的目标。
- ConcreteDecorator(具体装璜类):它是形象装璜类的子类,负责向构件增加新的职责。每一个具体装璜类都定义了一些新的行为,能够调用在形象装璜类中定义的办法,并能够减少新的办法用以裁减对象的行为。
3.3 实用场景
- 在不影响其余对象的状况下,以动静、通明的形式给单个对象增加职责。
- 当不能采纳继承的形式对系统进行扩大或者采纳继承不利于零碎扩大和保护时能够应用装璜模式。不能采纳继承的状况次要有两类:第 1 类是零碎中存在大量独立的扩大,为反对每一种扩大或者扩大之间的组合将产生大量的子类,使得子类数目呈爆炸性增长;第 2 类是因为类已定义为不能被继承(如 Java 语言中的 final 类)。
四、适配器模式
这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让本来因为接口不兼容而不能一起工作的类能够一起工作。
4.1 实现形式
-
类适配器:基于继承
public interface ITarget {void f1(); void f2(); void fc();} public class Adaptee {public void fa() {//...} public void fb() { //...} public void fc() { //...} } public class Adaptor extends Adaptee implements ITarget {public void f1() {super.fa(); } public void f2() {//... 从新实现 f2()... } // 这里 fc() 不须要实现,间接继承自 Adaptee,这是跟对象适配器最大的不同点}
-
对象适配器:基于组合
public interface ITarget {void f1(); void f2(); void fc();} public class Adaptee {public void fa() {//...} public void fb() { //...} public void fc() { //...} } public class Adaptor implements ITarget { private Adaptee adaptee; public Adaptor(Adaptee adaptee) {this.adaptee = adaptee;} public void f1() {adaptee.fa(); // 委托给 Adaptee } public void f2() {//... 从新实现 f2()... } public void fc() {adaptee.fc(); } }
4.2 核心内容
- Target(指标抽象类):指标抽象类定义客户所需接口,能够是一个抽象类或接口,也能够是具体类。
- Adapter(适配器类):适配器能够调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配。适配器类是适配器模式的外围,在对象适配器模式中,它通过继承 Target 并关联一个 Adaptee 对象使二者产生分割。
- Adaptee(适配者类):适配者即被适配的角色,它定义了一个曾经存在的接口,这个接口须要适配。适配者类个别是一个具体类,蕴含了客户心愿应用的业务办法,在某些状况下可能没有适配者类的源代码。
4.3 实用场景
- 封装有缺点的接口设计
- 对立多个类的接口设计
- 替换依赖的内部零碎
- 兼容老版本接口
- 适配不同格局的数据
五、门面模式
门面模式为子系统提供一组对立的接口,定义一组高层接口让子系统更易用。门面模式也称外观模式。
5.1 实现形式
- 简略演示
class Facade{private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();
public vodid method() {obj1.methodA();
obj2.methodB();
obj3.methodC();
}
}
- 具体实现
// 文件读取类:子系统类
class FileReader{public String read(String fileNameSrc){System.out.print("读取文件,获取明文:");
StringBuffer sb = new StringBuffer();
try{FileInputStream inFS = new FileInputStream(fileNameSrc);
int data;
while((data = inFS.read()) != -1){sb = sb.append((char)data);
}
inFS.close();
System.out.println(sb.toString());
}catch(FileNotFoundException e){System.out.println("文件不存在!");
}catch(IOException e){System.out.println("文件操作谬误!");
}
return sb.toString();}
}
// 数据加密类: 子系统类
class CipherMachine {public String encrypt(String plainText){System.out.print(" 数据加密, 将明文转换为密文:“);
String es = "";
for(int i = 0; i < plainText.length(); i++ ) {String c = String.valueOf(plainText.charAt(i) % 7);
es += c;
}
System.out.println(es);
return es;
}
}
// 文件保留类:子系统类
class FileWriter {public void write(String encryptStr,String fileNameDes){System.out.println("保留密文,写入文件。");
try{FileOutputStream outFS = new FileOutputStream(fileNameDes);
outFS.write(encryptStr.getBytes());
outFS.close();}catch(FileNotFoundExcept e){System.out.println("文件不存在!");
}catch(IOException e){System.out.println("文件操作谬误!");
}
}
}
// 加密外观类:外观类
class EncryptFacade {
// 维持对子系统对象的援用
private FileReader reader;
private CipherMachine cipher;
private FileWriter writer;
public EncryptFacade(){reader = new FileReader();
cipher = new CipherMachine();
writer = new FileWriter();}
// 调用其余对象的业务办法
public void fileEncrypt(String fileNameSrc,String fileNameDes){String plainStr = reader.read(fileNameSrc);
String encryptStr = cipher.encrypt(plainStr);
writer.write(encryptStr,fileNameDes);
}
}
// 客户端测试代码
class Client{public static void main(String args[]){EncryptFacade ef = new EncryptFacade();
ef.fileEncrypt("facade/src.txt","facade/des.txt");
}
}
5.2 核心内容
- Facade(外观角色):在客户端能够调用这个角色的办法,在外观角色中能够晓得相干的(一个或者多个)子系统的性能和责任。在失常状况下,它将所有从客户端发来的申请委派到相应的子系统中去,传递给相应的子系统对象解决。
- SubSystem(子系统角色):在软件系统中能够有一个或者多个子系统角色。每个子系统能够不是一个独自的类,而是一个类的汇合,它实现子系统的性能。每个子系统都能够被客户端间接调用,或者被外观角色调用,它解决由外观类传过来的申请。子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
5.3 实用场景
- 当要为拜访一系列简单的子系统提供一个简略入口时能够应用外观模式。
- 客户端程序与多个子系统之间存在很大的依赖性。引入外观类能够将子系统与客户端解耦,从而进步子系统的独立性和可移植性。
- 在层次化构造中,能够应用外观模式定义零碎中每一层的入口,层与层之间不间接产生分割,而通过外观类建立联系,升高层之间的耦合度。
六、组合模式
将一组对象组织(Compose)成树形构造,以示意一种“局部 – 整体”的层次结构。组合让客户端能够对立单个对象和组合对象的解决逻辑。
6.1 实现形式
// 形象构件角色
abstract class Component{public abstract void add(Component c);// 减少成员
public abstract void remove(Component c);// 删除成员
public abstract Component getChild(int i);// 获取成员
public abstract void operation();// 业务办法}
// 叶子构件
class Leaf extends Component{public void add(Component c){// 异样解决或谬误提醒}
public void remove(Component c){// 异样解决或谬误提醒}
public Component getÇhild(int i){// 异样解决或谬误提醒}
public void operation(){// 叶子构件具体业务办法的实现}
}
// 容器构件
class Composite extends Component{private ArrayList<Component> list = new ArrayList<Component>();
public void add(Component c){list.add(c);
}
public void remove(Component c){list.remove(c);
}
public Component getÇhild(int i){return (Component)list.get(i);
}
public void operation(){
// 容器构件具体业务办法的实现
// 递归调用成员构件的业务办法
for(Object obj:list){((Component)obj).operation();}
}
}
6.2 核心内容
- Component(形象构件):它能够是接口或抽象类,为叶子构件和容器构件对象申明接口,在该角色中能够蕴含所有子类共有行为的申明和实现。在形象构件中定义了拜访及治理它的子构件的办法,例如减少子构件、删除子构件、获取子构件等。
- Leaf(叶子构件):它在组合模式构造中示意叶子节点对象。叶子节点没有子节点,它实现了在形象构件中定义的行为。对于那些拜访及治理子构件的办法,能够通过捕捉异样等形式进行解决。
- Composite(容器构件):它在组合模式构造中示意容器节点对象。容器节点蕴含子节点,其子节点能够是叶子节点,也能够是容器节点。它提供一个汇合用于存储子节点,实现了在形象构件中定义的行为,包含那些拜访及治理子构件的办法,在其业务办法中能够递归调用其子节点的业务办法。
6.3 实用场景
- 在具备整体和局部的层次结构中,心愿通过一种形式疏忽整体与局部的差别,客户端能够一致性地看待它们。
- 在一个应用面向对象语言开发的零碎中须要解决一个树形构造。
- 在一个零碎中可能拆散出叶子对象和容器对象,而且它们的类型不固定,未来须要减少一些新的类型。
七、亨元模式
所谓“享元”,顾名思义就是被共享的单元。享元模式的用意是复用对象,节俭内存,前提是享元对象是不可变对象。
7.1 实现形式
享元模式的代码实现非常简单,次要是通过工厂模式,在工厂类中,通过一个 Map 或者 List 来缓存曾经创立好的享元对象,以达到复用的目标。
public class CharacterStyle {
private Font font;
private int size;
private int colorRGB;
public CharacterStyle(Font font, int size, int colorRGB) {
this.font = font;
this.size = size;
this.colorRGB = colorRGB;
}
@Override
public boolean equals(Object o) {CharacterStyle otherStyle = (CharacterStyle) o;
return font.equals(otherStyle.font)
&& size == otherStyle.size
&& colorRGB == otherStyle.colorRGB;
}
}
public class CharacterStyleFactory {private static final List<CharacterStyle> styles = new ArrayList<>();
public static CharacterStyle getStyle(Font font, int size, int colorRGB) {CharacterStyle newStyle = new CharacterStyle(font, size, colorRGB);
for (CharacterStyle style : styles) {if (style.equals(newStyle)) {return style;}
}
styles.add(newStyle);
return newStyle;
}
}
public class Character {
private char c;
private CharacterStyle style;
public Character(char c, CharacterStyle style) {
this.c = c;
this.style = style;
}
}
public class Editor {private List<Character> chars = new ArrayList<>();
public void appendCharacter(char c, Font font, int size, int colorRGB) {Character character = new Character(c, CharacterStyleFactory.getStyle(font, size, colorRGB));
chars.add(character);
}
}
7.2 核心内容
- Flyweight(形象享元类):通常是一个接口或抽象类,在形象享元类中申明了具体享元类公共的办法,这些办法能够向外界提供享元对象的外部数据(外部状态),同时也能够通过这些办法来设置内部数据(内部状态)。
- ConcreteFlyweight(具体享元类):它实现了形象享元类,其实例称为享元对象。在具体享元类中为外部状态提供了存储空间。通常,能够联合单例模式来设计具体享元类,为每个具体享元类提供惟一的享元对象。
- UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的形象享元类的子类都须要被共享,不能被共享的子类可设计为非共享具体享元类。当须要一个非共享具体享元类的对象时能够间接通过实例化创立。
- FlyweightFactory(享元工厂类):享元工厂类用于创立并治理享元对象,它针对形象享元类编程,将各种类型的具体享元对象存储在一个享元池中。享元池个别设计为一个存储“键值对”的汇合(也能够是其余类型的汇合),能够联合工厂模式进行设计。当用户申请一个具体享元对象时,享元工厂提供一个存储在享元池中已创立的实例,或者创立一个新的实例(如果不存在的话)并返回新创建的实例,同时将其存储在享元池中。
7.3 实用场景
- 一个零碎有大量雷同或者类似的对象,造成内存的大量消耗。
- 对象的大部分状态都能够内部化,能够将这些内部状态传入对象中。
- 在应用享元模式时须要保护一个存储享元对象的享元池,而这须要消耗肯定的系统资源。因而,在须要多次重复应用同一享元对象时才值得应用享元模式。