设计模式之美
如何评判代码质量
可维护性、可读性、可拓展性、灵活性、简洁性、可复用性、可测试性(单元测试)
设计模式分类
-
创建型
常用: 单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式
不常用:原型模式
-
结构型
常用:代理模式、桥接模式、装饰者模式、适配器模式
不常用:门面模式、组合模式、享元模式
-
行为型
常用:观察者模式、模板模式、策略模式、指责链模式、迭代器模式、状态模式
不常用:访问者模式、备忘录模式、命令模式、解释器模式、中介模式
抽象
屏蔽不需要关注的实现细节
继承
复用代码,但是深层次继承则不利于代码的阅读。
继承层次过深、继承关系过于复杂会影响到代码的可读性和可维护性。
多态
子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现
public SortedDynamicArray extends DynamicArray() {
public void test() {
//子类方法
}
}
DynamicArray dynamicArray = new SortedDynamicArray();
dynamicArray.test(); //调用SortedDynamicArray类的方法,也即子类的方法;
接口的多态:
public interface Iterator {
boolean hasNext();
String next();
String remove();
}
public class Array implements Iterator{
private String[] data;
@Override
public boolean hasNext() {
return true;
}
@Override
public String next() {
return null;
}
@Override
public String remove() {
return null;
}
}
public class LinkedList implements Iterator{
@Override
public boolean hasNext() {
return true;
}
@Override
public String next() {
return null;
}
@Override
public String remove() {
return null;
}
}
public class Test {
private static void print(Iterator iterator) {
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
public static void main(String[] args) {
Iterator arrayIterator = new Array();
print(arrayIterator);
Iterator linkedListIterator = new LinkedList();
print(linkedListIterator);
}
}
当再增加一种要遍历打印的类型的时候,比如HashMap,我们只需让 HashMap 实现 Iterator 接口,重新实现自己的 hasNext()、next()等方法就可以了,完全不需要改动 print() 函数的代码。所以说,多态提高了代码的可扩展性。
面向对象四大特性
封装:加装备(添加盔甲)
继承:师傅掌对掌传输武功(毫无保留)
抽象:从道到术,柳叶能伤人
多态:奥特曼变身
基于接口编程
“基于接口而非实现编程” 这条原则的另一个表述方式,是 “基于抽象而非实现编程” 。
越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对。
-
函数的命名不能暴露任何实现细节。比如,前面提到的 uploadToAliyun() 就不符合要求,应该改为去掉 aliyun 这样的字眼,改为更加抽象的命名方式,比如:upload()。
涉及图片处理和存储的业务逻辑实践:
public class Image {
}
/**
* @Description: 整个上传流程包含三个步骤:创建 bucket(你可以简单理解为存储目录)、
* 生成 access token 访问凭证、携带 access token 上传图片到指定的 bucket 中。
* 代码实现非常简单,类中的几个方法定义得都很干净,用起来也很清晰,乍看起来没有太大问题,
* 完全能满足我们将图片存储在阿里云的业务需求。
* @Author: yanzhihao
* @Date: 2022/1/20 2:34 下午
*/
public class AliyunImageStore {
public void createBucketIfNotExisting(String bucketName) {
// ... 创建 bucket 代码逻辑...
// ... 失败会抛出异常..
}
public String generateAccessToken() {
// ... 根据 accesskey/secrectkey 等生成 access token
return "";
}
public String uploadToAliyun(Image image, String bucketName, String accessTok) {
//... 上传图片到阿里云...
// ... 返回图片存储在阿里云上的地址 (url)...
return "";
}
public Image downloadFromAliyun(String url, String accessToken) {
//... 从阿里云下载图片...
return null;
}
}
public class ImageProcessingJob {
private static final String BUCKET_NAME = "ai_images_bucket";
public void process() {
Image image = new Image(); //处理图片,并封装为Image对象;
AliyunImageStore imageStore = new AliyunImageStore();
imageStore.createBucketIfNotExisting(BUCKET_NAME);
String accessToken = imageStore.generateAccessToken();
imageStore.uploadToAliyun(image, BUCKET_NAME, accessToken);
}
}
-
过一段时间后,业务场景发生变化,要将图片存储到私有云,而不是阿里云,这时候需要修改的代码就很多了
基于接口编程的优化方案如下:
public interface ImageStore { String upload(Image image, String bucketName); Image download(String url); } public class AliyunImageStore implements ImageStore { @Override public String upload(Image image, String bucketName) { // 上传图片到阿里云 // 返回图片在阿里云的地址 createBucketIfNotExisting(bucketName); return generateAccessToken(); } @Override public Image download(String url) { String accessToken = generateAccessToken(); return null; } private void createBucketIfNotExisting(String bucketName) { // ... 创建 bucket... // ... 失败会抛出异常.. } private String generateAccessToken() { // ... 根据 accesskey/secrectkey 等生成 access token return "token"; } } public class PrivateImageStore implements ImageStore{ @Override public String upload(Image image, String bucketName) { // 上传图片到私有云 // 返回图片url createBucketIfNotExisting(bucketName); return ""; } @Override public Image download(String url) { return null; } private void createBucketIfNotExisting(String bucketName) { // ... 创建 bucket... // ... 失败会抛出异常.. } } public class ImageProcessingJob { private static final String BUCKET_NAME = "ai_images_bucket"; // 上传到哪里直接修改imageStore实例化的代码即可 public void process() { Image image = new Image(); //处理图片,并封装为Image对象; ImageStore imageStore = new PrivateImageStore(); imageStore.upload(image, BUCKET_NAME); } }
多用组合少用继承
使用组合时如何像继承复用代码
public interface Flyable {
void fly();
}
public class FlyAbility implements Flyable{
@Override
public void fly() {
// fly
}
}
public class Ostrich implements Flyable{
//鸵鸟
private final FlyAbility flyAbility = new FlyAbility();
@Override
public void fly() {
flyAbility.fly();
}
}
贫血模型和充血模型(领域驱动设计)
-
贫血模型:传统MVC
-
充血模型:DDD(适合逻辑复杂的系统)
实践:虚拟钱包应用,支持用户充值提现、支付等
public class VirtualWallet { // Domain 领域模型 (充血模型) private Long id; private Long createTime = System.currentTimeMillis(); private BigDecimal balance = BigDecimal.ZERO; public VirtualWallet(Long preAllocatedId) { this.id = preAllocatedId; } public BigDecimal balance() { return this.balance; } public void debit(BigDecimal amount) throws InsufficientBalanceException { if (this.balance.compareTo(amount) < 0) { throw new InsufficientBalanceException(""); } this.balance.subtract(amount); } public void credit(BigDecimal amount) throws InvalidAmountException { if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new InvalidAmountException(""); } this.balance.add(amount); } } public class VirtualWalletEntity { } public class VirtualWalletRepository { public VirtualWalletEntity getWalletEntity(Long walletId) { return new VirtualWalletEntity(); } public BigDecimal getBalance(Long walletId) { return new BigDecimal(1); } public void updateBalance(Long walletId, BigDecimal balance) { } } public class VirtualWalletTransactionRepository { } public class VirtualWalletService {// 通过构造函数或者 IOC 框架注入 private VirtualWalletRepository walletRepo; private VirtualWalletTransactionRepository transactionRepo; public VirtualWallet getVirtualWallet(Long walletId) { VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId); VirtualWallet wallet = convert(walletEntity); return wallet; } public BigDecimal getBalance(Long walletId) { return walletRepo.getBalance(walletId); } // 增加某钱包金额 public void debit(Long walletId, BigDecimal amount) { VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId); VirtualWallet wallet = convert(walletEntity); wallet.debit(amount); walletRepo.updateBalance(walletId, wallet.balance()); } //减少某钱包金额 public void credit(Long walletId, BigDecimal amount) { VirtualWalletEntity walletEntity = walletRepo.getWalletEntity(walletId); VirtualWallet wallet = convert(walletEntity); wallet.credit(amount); walletRepo.updateBalance(walletId, wallet.balance()); } public void transfer(Long fromWalletId, Long toWalletId, BigDecimal amount) { } private VirtualWallet convert(VirtualWalletEntity walletEntity) { return new VirtualWallet(1L); } }
创建型
单例模式
-
饿汉式
启动即初始化
public class Car { private static final Car car = new Car(); public static Car getInstance() { return car; } private Car() { } public void run() { System.out.println("go go go !!!"); } }
-
懒汉式
第一次使用时初始化
public class Car { private static Car car2; /** * 线程安全增加 synchronized修饰 */ public static synchronized Car getInstance() { if (car2 == null) { System.out.println("第一次创建对象:买车了。。。"); car2 = new Car(); } return car2; } private Car() { } public void run(){ System.out.println("走"); } }
工厂模式(Factory)
工厂模式可以分为:简单工厂模式(也叫静态工厂模式)、工厂方法模式、以及抽象工厂模式。
工厂模式是编程中经常用到的一种模式。它的主要优点有:
- 可以使代码结构清晰,有效地封装变化。在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。
- 对调用者屏蔽具体的产品类。如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。
- 降低耦合度。产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。
总结:
- 简单工厂:根据if else 判断需要创建那个对象,直接return =>new 出来的对象
- 工厂方法:以定义接口定义实例化对象的方法,根据所要实例化的对象新建不同的工厂实现类,实现定义接口
- 抽象工厂:与工厂方法相似,知识抽象工厂中的工厂,只是每一个工厂有多个实例化方法,每个方法分别返回不同的对象
简单工厂模式
比较直接,直接通过传入的参数判断需要实例化的对象
public class GarageFactory {
public static IVehicle getVehicle(String type) {
if ("car".equals(type)) {
return new Car();
} else if ("motorcycle".equals(type)) {
return new Motorcycle();
}
throw new IllegalArgumentException("请输入车类型");
}
}
public static void main(String[] args) {
XiaoMing xiaoMing = new XiaoMing();
// 小明骑摩托车去学校
IVehicle motorcycle = GarageFactory.getVehicle("motorcycle");
xiaoMing.goToSchool(motorcycle);
// 小明开汽车去旅游
IVehicle car = GarageFactory.getVehicle("car");
xiaoMing.travel(car);
}
工厂方法模式
interface IFactory {
public ICar createCar();
}
class Factory implements IFactory {
public ICar createCar() {
Engine engine = new Engine();
Underpan underpan = new Underpan();
Wheel wheel = new Wheel();
ICar car = new Car(underpan, wheel, engine);
return car;
}
}
public class Client {
public static void main(String[] args) {
IFactory factory = new Factory();
ICar car = factory.createCar();
car.show();
}
}
使用该方法后,调用端的耦合度大大降低了。并且是可以扩展的,以后如果想组装其他的汽车,只需要再增加一个工厂类的实现就可以。无论是灵活性还是稳定性都得到了极大的提高。
抽象工厂模式
public interface IConfigParserFactory {
IRuleConfigParser createRuleParser();
ISystemConfigParser createSystemParser();
//此处可以扩展新的parser类型,比如IBizConfigParser
}
public class XmlConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new XmlRuleConfigParser();
}
@Override
public ISystemConfigParser createSystemParser() {
return new XmlSystemConfigParser();
}
}
public class JsonConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new JsonRuleConfigParser();
}
@Override
public ISystemConfigParser createSystemParser() {
return new JsonSystemConfigParser();
}
}
建造者模式(Builder)
需求:定义一个资源池配置类ResourcePoolConfig
public class ResourcePoolConfig {
private String name;
private int maxTotal;
private int maxIdle;
private int minIdle;
private ResourcePoolConfig(Builder builder) {
this.name = builder.name;
this.maxTotal = builder.maxTotal;
this.maxIdle = builder.maxIdle;
this.minIdle = builder.minIdle;
}
//...省略getter方法...
//我们将Builder类设计成了ResourcePoolConfig的内部类。
//我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
public static class Builder {
private static final int DEFAULT_MAX_TOTAL = 8;
private static final int DEFAULT_MAX_IDLE = 8;
private static final int DEFAULT_MIN_IDLE = 0;
private String name;
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig build() {
// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("...");
}
if (maxIdle > maxTotal) {
throw new IllegalArgumentException("...");
}
if (minIdle > maxTotal || minIdle > maxIdle) {
throw new IllegalArgumentException("...");
}
return new ResourcePoolConfig(this);
}
public Builder setName(String name) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("...");
}
this.name = name;
return this;
}
public Builder setMaxTotal(int maxTotal) {
if (maxTotal <= 0) {
throw new IllegalArgumentException("...");
}
this.maxTotal = maxTotal;
return this;
}
public Builder setMaxIdle(int maxIdle) {
if (maxIdle < 0) {
throw new IllegalArgumentException("...");
}
this.maxIdle = maxIdle;
return this;
}
public Builder setMinIdle(int minIdle) {
if (minIdle < 0) {
throw new IllegalArgumentException("...");
}
this.minIdle = minIdle;
return this;
}
}
}
public class Test {
public static void main(String[] args) {
ResourcePoolConfig config = new ResourcePoolConfig
.Builder() //内部静态类
.setName("db connection pool") //设置配置项
.setMaxTotal(16)
.setMaxIdle(10)
.setMinIdle(12)
.build(); //校验数据
}
}
原型模式
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式,简称原型模式。
原型模式有两种实现方法,深拷贝和浅拷贝。浅拷贝比较节省时间和内存。如果拷贝的对象是不可变的可以使用浅拷贝。
需求:同步数据库中关于搜索关键词的数据到内存中的HashMap中
系统背景:系统B定期更新新版本的关键词数据到数据库中
解决方案:
在系统 A中,记录当前数据的版本 Va 对应的更新时间 Ta,从数据库中捞出更新时间大于 Ta 的所有搜索关键词,也就是找出 Va 版本与最新版本数据的“差集”,然后针对差集中的每个关键词进行处理。如果它已经在散列表中存在了,我们就更新相应的搜索次数、更新时间等信息;如果它在散列表中不存在,我们就将它插入到散列表中;
增加特殊需求:然后又增加了一个特殊需求,需要保证系统A中的关键词数据都必须是同一个版本,要么都是版本a,要么都是版本b。
最终解决方案:
我们把正在使用的数据的版本定义为“服务版本”,当我们要更新内存中的数据的时候,我们并不是直接在服务版本(假设是版本 a 数据)上更新,而是重新创建另一个版本数据(假设是版本 b 数据),等新的版本数据建好之后,再一次性地将服务版本从版本 a 切换到版本 b。这样既保证了数据一直可用,又避免了中间状态的存在。
public class Demo {
private HashMap<String, SearchWord> currentKeywords = new HashMap<>();
private long lastUpdateTime = -1;
public void refresh() {
// Shallow copy
HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone();
// 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
long maxNewUpdatedTime = lastUpdateTime;
for (SearchWord searchWord : toBeUpdatedSearchWords) {
if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
maxNewUpdatedTime = searchWord.getLastUpdateTime();
}
if (newKeywords.containsKey(searchWord.getKeyword())) {
newKeywords.remove(searchWord.getKeyword());
}
newKeywords.put(searchWord.getKeyword(), searchWord);
}
lastUpdateTime = maxNewUpdatedTime;
currentKeywords = newKeywords;
}
private List<SearchWord> getSearchWords(long lastUpdateTime) {
// TODO: 从数据库中取出更新时间>lastUpdateTime的数据;
return null;
}
}
结构型
代理模式
动态代理:
所谓动态代理(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类
Spring Aop 底层原理基于动态代理
/**
* @Description: 动态代理
* @Author: yanzhihao
* @Date: 2022/1/25 1:47 下午
*/
public class MetricsCollectorProxy {
private MetricsCollector metricsCollector;
public MetricsCollectorProxy() {
this.metricsCollector = new MetricsCollector();
}
public Object createProxy(Object proxyObject) {
// 需要代理的类实现的所有接口
Class<?> [] interfaces = proxyObject.getClass().getInterfaces();
// 代理执行方法
DynamicProxyHandler handler = new DynamicProxyHandler(proxyObject);
// 将类加载器、需要代理的类实现的所有接口、代理执行方法handler 作为参数传入,创建代理对象
return Proxy.newProxyInstance(proxyObject.getClass().getClassLoader(), interfaces, handler);
}
private class DynamicProxyHandler implements InvocationHandler {
private Object proxyObject;
public DynamicProxyHandler(Object proxyObject) {
this.proxyObject = proxyObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTimestamp = System.currentTimeMillis();
// 执行需要代理的方法
Object result = method.invoke(proxyObject, args);
// 以上执行完成后,开始执行下面通过动态代理后附加的操作
long endTimestamp = System.currentTimeMillis();
long responseTime = endTimestamp - startTimestamp;
String apiName = proxyObject.getClass().getName() + ":" + method.getName();
RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTimestamp, endTimestamp);
metricsCollector.recordRequest(requestInfo);
return result;
}
}
}
public class Test {
public static void main(String[] args) {
MetricsCollectorProxy proxy = new MetricsCollectorProxy();
IUserController userController = (IUserController) proxy.createProxy(new UserController());
userController.getUser();
}
}
使用步骤:
- 新增动态代理自定义handle类用来编写代理逻辑,该类需要实现
InvocationHandler
重写invoke
方法,来实现具体的代理逻辑 - 利用Java特性便写生成代理对象的方法,即
Proxy.newProxyInstance(...)
- 使用代理对象通过
2
中的方法生成代理对象,调用需要代理的方法,这样就可以在原有方法的基础上增加代理类中invoke方法的逻辑
桥接模式
“将抽象和实现解耦,让它们能独立开发”
/**
* 业务背景:需要发送通知或警告,不同程度的通知或警告的逻辑不通
*/
public interface MsgSender {
void send(String message);
}
public class EmailMsgSender implements MsgSender{
private List<String> emilList;
public EmailMsgSender(List<String> emilList) {
this.emilList = emilList;
}
@Override
public void send(String message) {
// ... 邮件通知发送逻辑
}
}
public class TelephoneMsgSender implements MsgSender{
private List<String> telephones;
public TelephoneMsgSender(List<String> telephones) {
this.telephones = telephones;
}
@Override
public void send(String message) {
//... 手机信息通知发送逻辑
}
}
public abstract class Notification {
protected MsgSender msgSender;
public Notification(MsgSender msgSender) {
this.msgSender = msgSender;
}
public abstract void notify(String message);
}
public class SevereNotification extends Notification{
public SevereNotification(MsgSender msgSender) {
super(msgSender);
}
@Override
public void notify(String message) {
msgSender.send(message);
}
}
public class UrgencyNotification extends Notification{
public UrgencyNotification(MsgSender msgSender) {
super(msgSender);
}
@Override
public void notify(String message) {
msgSender.send(message);
}
}
装饰器模式
参考Java源码
BufferedInputStream
、InputStream
、FilterInputStream
三个类之间的关系
其中
FilterInputStream
类对InputStream
类中的方法进行了装饰,避免了代码重复
//示例非源码
public class BufferedInputStream {
protected volatile InputStream in;
protected BufferedInputStream(InputStream in) {
this.in = in;
}
// f()函数不需要增强,只是重新调用一下InputStream in对象的f()
public void f() {
in.f();
}
}
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。 为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
适配器模式
使用适配器模式 封装外部缺陷接口/方法
/**
* @Description: 外部sdk
* @Author: yanzhihao
* @Date: 2022/2/7 5:26 下午
*/
public class CD {
public void staticFunction1() {}
public void uglyNamingFunction2() {}
public void tooManyParamsFunction3(int paramA, int paramB, int... args) {}
public void lowPerformanceFunction4() {}
}
public class ParamsWrapperDefinition {
public int getParamA;
public int getParamB;
public int getParamC;
}
/**
* 适配器接口定义,对sdk进行重构的定义
*/
public interface ITarget {
void function1();
void function2();
void function3(ParamsWrapperDefinition paramWrapper);
void function4();
}
/**
* 使用适配器模式 封装外部缺陷接口/方法
* @Description:
* @Author: yanzhihao
* @Date: 2022/2/7 5:32 下午
*/
public class CDAdaptor extends CD implements ITarget {
@Override
public void function1() {
super.staticFunction1();
}
@Override
public void function2() {
super.uglyNamingFunction2();
}
@Override
public void function3(ParamsWrapperDefinition paramWrapper) {
super.tooManyParamsFunction3(paramWrapper.getParamA, paramWrapper.getParamB, paramWrapper.getParamC);
}
@Override
public void function4() {
super.lowPerformanceFunction4();
}
}
统一多个类的接口设计
/**
* 使用适配器模式
* 统一的接口定义
*/
public interface ISensitiveWordsFilter {
String filter(String text);
}
public class ASensitiveWordsFilter {
public String filterSexyWords(String text) {
return "";
}
public String filterPoliticalWords(String maskedText) {
return "";
}
}
public class ASensitiveWordsFilter {
public String filterSexyWords(String text) {
return "";
}
public String filterPoliticalWords(String maskedText) {
return "";
}
}
public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {
private ASensitiveWordsFilter aFilter;
@Override
public String filter(String text) {
String maskedText = aFilter.filterSexyWords(text);
maskedText = aFilter.filterPoliticalWords(maskedText);
return maskedText;
}
}
public class RiskManagement {
private List<ISensitiveWordsFilter> filters = new ArrayList<>();
public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
filters.add(filter);
}
public String filterSensitiveWords(String text) {
String maskedText = text;
for (ISensitiveWordsFilter filter : filters) {
maskedText = filter.filter(maskedText);
}
return maskedText;
}
}
替换依赖的外部系统
public interface IA {
void fa();
}
public class A implements IA{
//...
@Override
public void fa() {
//...
}
}
public interface IB {
void fb();
}
public class B implements IB{
@Override
public void fb() {
// ...
}
}
public class BAdaptor implements IA {
private B b;
public BAdaptor(B b) {
this.b = b;
}
@Override
public void fa() {
// ...
b.fb();
}
}
public class Demo {
private IA a;
public Demo(IA a) {
this.a = a;
}
//...
}
public class Test {
public static void main(String[] args) {
//一开始使用外部A系统
Demo a = new Demo(new A());
//将A系统更换为外部B系统
Demo b = new Demo(new BAdaptor(new B()));
}
}
兼容老版本接口
public class Emueration {
}
public class Collections {
public static Emueration emueration(final Collection c) {
return new Emueration() {
Iterator i = c.iterator();
public boolean hasMoreElements() {
return i.hasNext();
}
public Object nextElement() {
return i.next();
}
};
}
}
适配不同格式的数据
public class Test {
public static void main(String[] args) {
List<String> strings = Arrays.asList("1", "2", "3");
}
}
总结
尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。
代理模式: 代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
桥接模式: 桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
装饰器模式: 装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
适配器模式: 适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。
门面模式
门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。
- 解决易用性问题
- 解决性能问题
- 解决分布式事务问题
组合模式
组合模式,将一组对象组织成树形结构,将单个对象和组合对象都看做树中的节点,以统一处理逻辑,并且它利用树形结构的特点,递归地处理每个子树,依次简化代码实现。使用组合模式的前提在于,你的业务场景必须能够表示成树形结构。所以,组合模式的应用场景也比较局限,它并不是一种很常用的设计模式。
享元模式
所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。具体来讲,当一个系统中存在大量重复对象的时候,我们就可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用,这样可以减少内存中对象的数量,以起到节省内存的目的。实际上,不仅仅相同对象可以设计成享元,对于相似对象,我们也可以将这些对象中相同的部分(字段),提取出来设计成享元,让这些大量相似对象引用这些享元。
享元模式的代码实现非常简单,主要是通过工厂模式,在工厂类中,通过一个 Map 或者List 来缓存已经创建好的享元对象,以达到复用的目的。
-
享元模式再Integer和String中都有应用
- Integer中缓存了 -128~127的值
- String中缓存了内存中已用字符串
行为型
观察者模式
观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在 GoF 的《设计模式》一书中,它的定义是这样的:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Message message);
}
public interface Observer {
void update(Message message);
}
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(Message message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
public class ConcreteObserverOne implements Observer {
@Override
public void update(Message message) {
//TODO: 获取消息通知,执行自己的逻辑...
System.out.println("ConcreteObserverOne is notified.");
}
}
public class ConcreteObserverTwo implements Observer {
@Override
public void update(Message message) {
//TODO: 获取消息通知,执行自己的逻辑...
System.out.println("ConcreteObserverTwo is notified.");
}
}
/**
* 观察者模式
* @author yanzhihao
* @since 2022/5/7
*/
public class Demo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.registerObserver(new ConcreteObserverOne());
subject.registerObserver(new ConcreteObserverTwo());
subject.notifyObservers(new Message());
}
}
模板模式
模板模式,全称是模板方法设计模式,英文是 Template Method Design Pattern。在GoF 的《设计模式》一书中,它是这么定义的:
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
翻译成中文就是:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
/**
* 模板模式
* @author yanzhihao
* @since 2022/5/7
*/
public abstract class AbstractClass {
public final void templateMethod() {
//...
method1();
//...
method2();
}
protected abstract void method1();
protected abstract void method2();
}
public class ConcreteClass1 extends AbstractClass {
@Override
protected void method1() {
//...
}
@Override
protected void method2() {
//...
}
}
public class ConcreteClass2 extends AbstractClass {
@Override
protected void method1() {
//...
}
@Override
protected void method2() {
//...
}
}
public class Demo {
public static void main(String[] args) {
AbstractClass demo = new ConcreteClass1();
demo.templateMethod();
}
}
-
复用:InputStream、AbstractList
- Java IO 类库中,有很多类的设计用到了模板模式,比如 InputStream、OutputStream、Reader、Writer
- Java AbstractList 类中,addAll() 函数可以看作模板方法
-
扩展:Servlet、JUnit TestCase
模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。