读书笔记:信任是职场发展的真正原动力,当同事之间有了信任基础,即使出了事大家也都好商量。而高难度沟通最直接的目的就是化解分歧,调和情绪,通过寻求解决方案达成一致,而不是相互指责甩锅,最终赢得彼此信任。信任=(专业性+可靠性+关联关系)/自我。除了提升我们的专业技能,踏实可靠稳定的品行,以及提升同事之间的日常互动,需要注意降低自我自负的表现。
一、前言背景
二、设计模式三大类型
2.1 创建型模式
2.2 结构型模式
2.3 行为型模式
三、创建型模式全面详解
3.1 建造者模式
3.1.1 实战demo-场景
3.1.2 建造者模式的优点
3.2 原型模式
3.2.1 实战demo-深拷贝&浅拷贝场景
3.2.2 原型模式的优点
3.3 单例模式
3.4 工厂方法
3.4.1 实战demo-场景
3.4.2 工厂方法的优点
3.5 抽象工厂
3.5.1 实战demo-场景
3.5.2 抽象工厂的优点
四、创建型模式的意义
之前系列1-2说过,设计模式有6大核心原则,SOLID(单一职责、开闭、里氏替换、接口隔离、依赖倒置原则)+迪米特法则,它的核心思想就是要面向接口编程,为模块对象的交互解耦。而具体的设计模式20多种,但根据具体功能和用途,设计模式可以大体分为三大类型:创建型模式、结构型模式、行为型模式。
在系列2里我们详解的单例模式、外观(门面)模式、模板方法模式,刚好分别对应属于上述三大类型。
今天我们一口气讲完创建型设计模式的具体5个模式,同样每个模式都结合demo案例分析,这样可以更系统直观高效率地去了解掌握相似功能作用设计模式的特点和适用场景。
理想情况下,如果可以保持这个写作分享效率,接下来大概3篇文章,我们就可以很惊喜的总结分享完毕全部20多种设计模式【理想和现实总是有差距,其实我也希望可以做到....】。
创建型模式主要是关注对象创建的过程,而隐藏了对象创建的细节,进而达到提高代码可维护性、可扩展性目标。
创建型模式有5种:
结构型模式,对类和具体对象的组合关系特别关注,往往通过继承、聚合、组合来实现代码的复用和解耦。结构型模式有7种,由于在本文不是重点,只陈列名单:
行为模式,特别关注实例对象之间的交互,它通过定义对象之间信息传递和行为来提高代码的可复用性和灵活性。该大类型具体模式比较多,有11种:
附:三大类型的定义和划分,不同作者和书籍划分会有一些出入,尤其是行为型和结构型的模式,这主要取决于在实践应用设计解决问题的出发点不同。基于此同样一个业务场景,亦可以用多种设计模式去实践解决。
它的主要特点是通过分步骤去构建复杂的对象,将实例对象的构建过程和表示分离开。客户端在需要实例化对象的时候,无需了解具体对象内部细节,只需要通过建造者builder就可以完成构建。
典型应用有lombok.Builder的@builder注解。
还有mybaties源码里解析配置文件的三大XML builder类。
建造者模式,适合用于对象构建逻辑复杂,或者需要多个步骤才能完成构建的场景。
比如需要实例化一个电脑产品,电脑里面有内存、cpu、磁盘、主板等模块属性,通过建造者去实现demo如下。
package lading.java.designpattern.builder;
public class Computer {
private String cpu;
private String memory;
private String storage;
private String mainBoard;
//避免其他地方实例化
private Computer() {
}
public String getCpu() {
return cpu;
}
public String getMemory() {
return memory;
}
public String getStorage() {
return storage;
}
public String getMainBoard() {
return mainBoard;
}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + '\'' +
", memory='" + memory + '\'' +
", storage='" + storage + '\'' +
", mainBoard='" + mainBoard + '\'' +
'}';
}
//建造者builder
public static class Builder {
private Computer computer = new Computer();
public Builder setCpu(String cpu) {
computer.cpu = cpu;
return this;
}
public Builder setMemory(String memory) {
computer.memory = memory;
return this;
}
public Builder setStorage(String storage) {
computer.storage = storage;
return this;
}
public Builder setMainBoard(String mainBoard) {
computer.mainBoard = mainBoard;
return this;
}
public Computer build() {
//这里还可以定义一些步骤去组织实例化该对象。。。
return computer;
}
}
}
package lading.java.designpattern.builder;
/**
* 客户端调用,无需关注Computer对象的内部细节,
* 构建Computer只需要通过它的建造者 Computer.Builder()去处理。
*/
public class ClientDemo {
public static void main(String[] args) {
Computer computer = new Computer.Builder().setStorage("磁盘xx")
.setCpu("cpu xx").setMainBoard("主板xx").setMemory("内存xx")
.build();
System.out.println(computer.toString());
}
}
建造者由于隐藏了对象复杂的构建过程和细节,简化了客户端代码,提高了代码灵活性,研发人员只需要通过实现不同的建造者builder,就可以构建不同的实例对象(目标产品对象的表示)。
此外建造者的出现,也可以省去多个不同参数列表的对象构造方法。
最后,通过链式调用设置参数构建对象,也提高了代码的可读性。
原型模式的定义非常简单,通过复制一个对象,去克隆多个与原对象一模一样的对象。
什么场景需要这样的模式去构建对象?
比如有一些对象构建过程是非常复杂的,为了降低创建的成本,直接复用原对象的属性、状态,通过原型模式,可以避免重复的对象创建和初始化。
对象的复制克隆有深拷贝和浅拷贝两种方式。浅拷贝只是复制基本类型和地址引用,深拷贝就会递归复杂引用对象,达到原对象和新对象的隔离。
原型模式,JDK的Cloneable接口、Object.clone()都做了支持。比如ArrayList.clone()就是浅拷贝。还有spring框架的Bean实例化原型模式(prototype),就是在每次请求的时候,生成对应实例,但是底层是通过克隆去实现。
举个简单例子,比如有个user对象,需要复制克隆。如下浅拷贝后,对象的引用对象是复制了地址,如果原始对象变化了,浅拷贝的新对象也会被影响,而深拷贝就不会被污染。
package lading.java.designpattern.prototype;
import java.util.ArrayList;
import java.util.List;
/**
* 一个用户
* 名字
* 衣柜的衣服,有多件衣服
*/
public class User implements Cloneable {
private String name;
//衣柜的衣服
private List<String> cloths;
public User(String name, List<String> cloths) {
this.name = name;
this.cloths = cloths;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", cloths=" + cloths +
'}';
}
//浅拷贝
@Override
public User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
public User deepClone() throws CloneNotSupportedException {
User newUser = (User) super.clone();
//对引用对象,递归复制克隆
newUser.cloths = new ArrayList<>(this.cloths);
return newUser;
}
public static void main(String[] args) throws CloneNotSupportedException {
List<String> cloths = new ArrayList<>();
cloths.add("衬衫");
cloths.add("裤子");
User sourceUser = new User("拉丁解牛", cloths);
System.out.println("原始对象值:" + sourceUser);
//浅拷贝deepCopy
User shallowCopy = sourceUser.clone();
//深拷贝deepCopy
User deepCopy = sourceUser.deepClone();
//在原始对象里新增T恤
sourceUser.cloths.add("T恤");
//浅拷贝对象同样被污染
System.out.println("浅拷贝的对象被污染,多了一个T恤:" + shallowCopy);
System.out.println("浅拷贝的对象没有被污染,保持和原对象一致:" + deepCopy);
}
}
结果如下:
浅拷贝的对象,拷贝完成后,原对象再次修改自己内部属性,被动多了一个T恤:
由于直接复制克隆比new 更高效,提升了性能。
此外也简化了对象构建,让应用的时候无需关注对象内部细节,直接复制克隆完成对象构建。
该模式在状态保存场景应用,深克隆拷贝让备份和历史恢复的实现,非常方便。
单例模式也很简单,里面的懒汉模式、饿汉模式,经典的双重检查实现在系列02里《这几种设计模式很简单实用 | 相信你肯定见过》我们有详细分享过,麻烦移步第二篇浏览指正,这里不再赘述。
工厂方法模式,源于工厂生产产品的场景,具体是通过定义工厂抽象类、产品接口,把构建产品对象实例的工厂,延迟到工厂子类去实现。
传统产品生产方式,可能就是在客户端去new新增一个个实例。然而工厂方法模式通过引入工厂抽象类,一个工厂就可以生产N种产品,而且不需要在客户端应用修改,通过新增工厂子类就可以生产多种产品,简化客户端的逻辑。相比简单的工厂模式(通过代码分支if else、switch去构建不同的实例)显得简洁优雅,以及大幅提高了代码可维护性。
比如JDBC的Driver.getConnection()根据url自动选择数据库驱动。还有spring的BeanFactory接口,定义了getBean()方法,然后子类去实现具体的对象实例构建。
比如通过工厂去生产不同品牌的电脑。通过定义一个抽象工厂类AbstractFactory,还有一个IComputer接口来定好工厂方法骨架,后面具体品牌的电脑产品以及对应的工厂子类可以自定义扩展实现。
核心的抽象工厂类+电脑产品接口:
package lading.java.designpattern.factorymethod;
/**
* 抽象工厂类
*/
public abstract class AbstractComputerFactory {
//定义工厂抽象方法,生产电脑
abstract IComputer createComputer();
//定义一些公共功能,让子类复用
void init(){
System.out.println("工厂初始化");
}
}
package lading.java.designpattern.factorymethod;
public interface IComputer {
//生产电脑
void produce(String computerName);
}
具体的工厂子类去生产具体某个产品(苹果电脑工厂子类+苹果电脑产品):
package lading.java.designpattern.factorymethod;
/**
* 具体的工厂子类
*/
public class AppleComputerFactory extends AbstractComputerFactory {
@Override
public IComputer createComputer() {
return new AppleComputer();
}
}
package lading.java.designpattern.factorymethod;
/**
* 具体的产品类
*/
public class AppleComputer implements IComputer{
@Override
public void produce(String computerName) {
System.out.println("苹果电脑工厂,开始生产:"+computerName);
}
}
客户端调用生产产品demo:
package lading.java.designpattern.factorymethod;
public class FactoryMethodClientDemo {
public static void main(String[] args) {
//实例化具体苹果工厂子类
AbstractComputerFactory computerFactory = new AppleComputerFactory();
//复用工厂初始化操作
computerFactory.init();
//通过苹果工厂去生产苹果电脑
IComputer appleComputer = computerFactory.createComputer();
appleComputer.produce("macBook Pro");
}
}
当需要新增生产线,比如新增华为电脑产品,只需要新增对应的工厂子类和产品类就可以实现,对原先其他产品和工厂无影响。
首先通过定义抽象的工厂接口去实现产品实例对象生产,让客户端与具体的实例解耦。
此外也提高了代码扩展性,后续新增其他类型产品,只需要新增对应扩展工厂子类,对其他无影响。
抽象工厂类也可以提供一些通用逻辑,提高代码在工厂子类的复用性。
抽象工厂放在工厂方法模式之后,也是为了方便更好的区别和应用。在工厂方法里,如果现在需要新增华为电脑生产,需要新增一个华为工厂子类,以及新增华为产品,如果新增20个品牌电脑,就需要新增对应20个实现类。如果让苹果工厂不仅可以生产苹果电脑,还可以生产苹果耳机,华为工厂生产华为耳机。在工厂方法模式里,就需要对全部工厂子类修改,这样看起并不方便,这时候抽象工厂应运而生。
针对需要生产多种产品,而且每种产品还有细分类型,比如刚才这个例子,抽象工厂模式,通过定义一个工厂接口,里面可以生产电脑产品、耳机产品。然后每个品牌抽象成一个接口,工厂接口只负责生产对应品牌,具体的品牌子类再去生产产品。
抽象工厂模式,是典型的高内聚低耦合的设计。高内聚体现在子工厂(品牌)和具体产品都独立封装了自己的方法实现。低耦合在于工厂接口、产品接口的设计,让业务模块彼此隔离。
定义一个工厂接口、生产电脑接口、生产手机的接口。
package lading.java.designpattern.abstractfactory;
/**
* 工厂接口,支持生产不同种类的产品
* 具体的生产线(品牌),在子类中实现
*/
public interface IFactory {
//生产电脑
IComputer makeComputer();
//生产手机
IMobilePhone makePhone();
}
package lading.java.designpattern.abstractfactory;
/**
* 不同类型产品,定义为接口
* 电脑生产线
*/
public interface IComputer {
void produce();
}
package lading.java.designpattern.abstractfactory;
/**
* 手机生产线,生产手机产品
*/
public interface IMobilePhone {
void produce();
}
然后,定义实现对应的品牌工厂子类、还有具体产品生产线。
package lading.java.designpattern.abstractfactory;
/**
* 具体工厂子类(具体品牌)
* 华为工厂,生产具体的电脑、手机
*/
public class HuaweiFactory implements IFactory{
@Override
public IComputer makeComputer() {
//生成自己品牌对象
return new HuaweiComputer();
}
@Override
public IMobilePhone makePhone() {
//生成自己品牌对象
return new HuaweiMobilePhone();
}
}
package lading.java.designpattern.abstractfactory;
public class HuaweiMobilePhone implements IMobilePhone{
@Override
public void produce() {
System.out.println("生产华为手机");
}
}
package lading.java.designpattern.abstractfactory;
/**
* 华为品牌,生产有电脑和手机
*/
public class HuaweiComputer implements IComputer{
@Override
public void produce() {
System.out.println("生产华为电脑");
}
}
最后,客户端调用demo:
package lading.java.designpattern.abstractfactory;
/**
* 抽象工厂demo客户端
*/
public class AbstractFactoryDemo {
public static void main(String[] args) {
//华为工厂,生产手机、电脑
IFactory factory = new HuaweiFactory();
factory.makeComputer().produce();
factory.makePhone().produce();
//如果新增其他品牌,新增对应xxFactory。
//如果生产线内部修改,只需要改自己内部对客户端无影响
}
}
客户端调用工厂生产产品,由于只需要依赖接口,无需关心具体工厂子类实现,对具体业务细节实现解耦。此外,后续单类型工厂产品族新增产品,或者新增多个品牌,客户端调用,以及对应子类扩展都很方便。
回头看建造者模式、原型模式、单例模式、工厂方法、抽象工厂五种模式的demo示例,我们总结发现:复杂的对象构建、对象状态属性保存复用、对象实例的全局共享、多类型动态的对象构建,和传统一般的实例构建有着更高的复用性要求。创建型的五种设计模式,通过接口或者工厂方法增强了系统可扩展性和灵活性,让研发者在客户端可以优雅的管理对象的构建,以及在后续扩展中提供了便利。
推荐阅读拉丁解牛相关专题系列(欢迎交流讨论公众号搜:拉丁解牛说技术):
1、JVM进阶调优系列(5)CMS回收器通俗演义一文讲透FullGC
2、JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
4、JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?
6、JAVA并发编程系列(13)Future、FutureTask异步小王子
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。