第 1 章 设计模式的原则
- 单一职责原则:降低类的复杂度,一个类应该只有一个职责
- 里氏替换原则:子类在继承时尽量不要重写父类的方法,如果子类迫不得已要重写,就让子类和父类去继承更通俗的基类来降低二者的耦合性
- 依赖倒置原则:高层模块类不依赖低层模块类,两者都依赖其抽象接口
- 接口隔离原则:客户端只依赖它需要的接口,两个类间的依赖关系应该建立在最小的接口上
- 迪米特法则:如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用;如果一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用
- 开闭原则:一个类应当对提供方的扩展开放,对使用方的修改关闭。在设计一个模块的时候,应当在不必修改源代码的情况下改变这个模块的行为
- 组合 / 聚合复用原则:尽量使用组合和聚合少使用继承的关系来达到复用
如果公司规定司机入驻时必须有一辆自己的车才能去载客,此时 Driver 和 Car 是组合关系:
public class Driver {
public Car car;
public Driver() {
car = new Car(); // 司机自己提供车
}
public void drive() {
car.start();
}
}
如果公司规定只要司机会开车,公司各种车任你挑去载客,此时 Driver 和 Car 是聚合关系:
public class Driver {
public Car car;
public void setCar(Car car){ // 由公司提供,什么车司机不清楚
this.car = car;
}
public void drive() {
car.start();
}
}
第 2 章 UML 类图
2.1 具体类
具体类在类图中用矩形框表示,矩形框分为三层:第一层是类名字。第二层是类的成员变量;第三层是类的方法。成员变量以及方法前的访问修饰符用符号来表示:+ 表示 public,- 表示 private。
2.2 抽象类
抽象类用矩形框表示,抽象类的类名以及抽象方法的名字都用斜体字表示。
2.3 接口
接口在类图中也是用矩形框表示,但是与类的表示法不同的是,接口在类图中的第一层顶端用构造型 <> 表示,下面是接口的名字,第二层是方法。
2.4 六种关系
2.4.1 实现关系
实现关系是指接口及其实现类之间的关系:
2.4.2 泛化关系(继承关系)
泛化关系是指对象与对象之间的继承关系:
2.4.3 关联关系
关联关系是指一个对象含有另一个对象的引用:
2.4.4 依赖关系
如果对象 A 用到对象 B ,但是和 B 的关系不是太明显的时候,就可以把这种关系看作是依赖关系:
2.4.5 聚合关系
2.4.6 组合关系
第 3 章 单例模式
单例模式保证某个类只能存在一个对象实例,并且该类只提供一个获取对象实例的静态方法。
3.1 饿汉式实现
- 优点:线程安全,没有加锁
- 缺点:类加载时就加载了实例,如果没使用过该实例会浪费内存
class Singleton {
// 1.私有化构造器
private Singleton() {}
// 2.类的内部创建静态化对象 instance
private static Singleton instance = new Singleton();
// 3.提供公共的静态方法 getInstance,返回当前类的对象 instance
public static Singleton getInstance() {
return instance;
}
}
3.2 懒汉式实现(双重加锁 DCL)
实际开发中要保证线程安全,所以使用双重检查模式的懒汉式。
- 优点:第一次调用对象才初始化,避免内存浪费
- 缺点:必须加锁才能保证单例,但加锁会影响效率
class Singleton{
// 1.私有化构造器
private Singleton() {}
// 2.类的内部创建静态化对象 instance
private static volatile Singleton instance;
// 3.提供公共的静态方法 getInstance,如果未创建对象则在方法内部创建
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
3.3 静态内部类实现
内部类只有在外部类被调用才加载,产生 INSTANCE 实例;又不用加锁,是最好的单例模式。使用内部类来实现单例模式如下:
public class Singleton {
private Singleton(){}
private static class Inner {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return Inner.INSTANCE;
}
}
3.4 枚举类实现
不仅避免多线程同步问题,而且还能防止反序列化重新创建对象(JVM 保证枚举类型不能被反射并且构造函数只被执行一次):
public enum Singleton {
INSTANCE;
public void getInstance() {
System.out.println("我是一个单例!");
}
}
第 4 章 抽象工厂模式
抽象工厂模式同工厂方法模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成。
- 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品
- 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建
- 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品
- 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系
我们使用抽象工厂模式模仿生产小米手机、华为手机、小米路由器、华为路由器:
4.1 抽象工厂
抽象工厂中包含创建手机和创建路由器的两个方法:
public interface ProductFactory {
Phone getPhone();
Router getRouter();
}
4.2 具体工厂
实现抽象工厂中的两个抽象方法,完成小米手机、小米路由器、华为手机、华为路由器的产品创建。
小米工厂:
public class XiaomiFactory implements ProductFactory {
@Override
public Phone getPhone() {
return new XiaomiPhone();
}
@Override
public Router getRouter() {
return new XiaomiRouter();
}
}
华为工厂:
public class HuaweiFactory implements ProductFactory {
@Override
public Phone getPhone() {
return new HuaweiPhone();
}
@Override
public Router getRouter() {
return new HuaweiRouter();
}
}
4.3 抽象产品
定义了产品的规范,描述了产品的主要特性和功能。
手机接口定义了拍照片和发短信两个功能:
public interface Phone {
void photograph();
void sendMessage();
}
路由器接口定义了开 wifi 和关 wifi 两个功能:
public interface Router {
void openWifi();
void closeWifi();
}
4.4 具体产品
实现抽象产品角色所定义的接口。
小米手机:
public class XiaomiPhone implements Phone {
@Override
public void photograph() {
System.out.println("小米手机拍照片");
}
@Override
public void sendMessage() {
System.out.println("小米手机发短信");
}
}
小米路由器:
public class XiaomiRouter implements Router {
@Override
public void openWifi() {
System.out.println("小米路由器打开WiFi");
}
@Override
public void closeWifi() {
System.out.println("小米路由器关闭WiFi");
}
}
华为手机和华为路由器同理。
第 5 章 适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
比如美国电器电压是 110V,中国是 220V,就要有一个适配器将 110V 转化为 220V。
5.1 需要被适配的类
public class Voltage110V {
public int output110V(){
return 110;
}
}
5.2 适配器接口
public interface Voltage220V {
int output220V();
}
5.3 适配后的类
public class VoltageAdapter implements Voltage220V {
private Voltage110V voltage110V;
public VoltageAdapter(Voltage110V voltage110V) {
this.voltage110V = voltage110V;
}
@Override
public int output220V() {
int dstV = 0;
if (voltage110V != null) {
int srcV = voltage110V.output110V();
dstV = srcV * 2;
}
return dstV;
}
}
5.4 调用
public class Phone {
public void charging(Voltage220V voltage220V){
if(voltage220V.output220V() == 220){
System.out.println("电压是220V,可以使用");
}else {
System.out.println("电压不是220V,不可以使用");
}
}
}
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage110V()));
}
}
第 6 章 代理模式
动态代理在内存中构建代理对象。代理类所在包 java.lang.reflect.proxy,JDK 代理使用 newProxyInstance 方法,接收三个参数 ClassLoader loader,Class<?>[] interfaces,InvocationHandler h
6.1 JDK 代理
目标对象需要实现接口,使用 JDK 代理。
6.1.1 需要代理的接口和其实现类
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
}
public class CalculatorLogImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
}
6.1.2 生产代理对象的工厂类 ProxyFactory
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target; // 依赖目标对象
}
// getProxy() 方法返回代理对象 Proxy.newProxyInstance()
public Object getProxy() {
// 参数 1:加载动态生成的代理类的类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
// 参数 2:目标对象实现的所有接口的 class 类型数组
Class<?>[] interfaces = target.getClass().getInterfaces();
// 参数 3:设置代理对象实现目标对象方法的过程
InvocationHandler invocationHandler = new InvocationHandler() {
// invoke() 代理类中重写接口中的抽象方法
/**
* proxy:代理对象
* method:代理对象需要实现的方法,即需要重写的方法
* args:method所对应方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 方法调用前输出日志
System.out.println("[日志] " + method.getName() + ",参数:" + Arrays.toString(args));
// 调用目标方法
result = method.invoke(target, args);
// 方法调用后输出日志
System.out.println("[日志] " + method.getName() + ",结果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[日志] " + method.getName() + ",异常:" + e.getMessage());
} finally {
System.out.println("[日志] " + method.getName() + ",方法执行完毕");
}
return result;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
}
}
6.1.3 测试
public class CalculatorTest {
public static void main(String[] args) {
// 创建代理对象(动态):同一个代理对象可以代理多个被代理对象
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
// 返回代理对象
Calculator calculator = (Calculator) proxyFactory.getProxy();
calculator.add(1,2);
}
}
6.2 CGLib 代理
目标对象不需要实现接口,使用 CGLib 代理。底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类。
6.2.1 引入 CGLib 的依赖
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
6.2.2 目标对象
public class CalculatorDao {
public void calculate(){
System.out.println("计算,使用CGLib代理");
}
}
6.2.3 生产代理对象的工厂类 ProxyFactory
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target; // 依赖目标对象
}
public Object getProxyInstance(){
Enhancer enhancer = new Enhancer(); // 创建工具类
enhancer.setSuperclass(User.class); // 设置 enhancer 的父类
enhancer.setCallback(new MyProxy()); // 设置 enhancer 的回调对象
return enhancer.create();
}
/**
* @param o cglib 生成的代理对象
* @param method 被代理对象的方法
* @param objects 传入方法的参数
* @param methodProxy 代理的方法
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLib代理开始");
Object returnVal = method.invoke(target, objects);
System.out.println("CGLib代理结束");
return returnVal ;
}
}
6.2.4 测试
public class TestCalculator {
public static void main(String[] args) {
// 创建代理对象(动态):同一个代理对象可以代理多个被代理对象
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorDao());
// 返回代理对象
CalculatorDao proxyInstance = (CalculatorDao) proxyFactory.getProxy();
proxyInstance.calculate();
}
}
第 7 章 模板模式
用户看直播是有一套固定的模板流程的:登录—进入房间—获取音视频流—观看—停止音视频流—退出房间 虽然两套 SDK 每一个步骤的实现方式不同,但是基本都是遵循同一套流程。这时候就可以用到模板模式。
7.1 抽象类
先定义一个模板类 LivePlay,其中 seeLivePlay() 就是所谓的模板方法,为了不被子类重写,它被设置为 final 的,其定义了一个算法骨架:
public abstract class LivePlay {
// 模板方法
public final void seeLivePlay() {
login();
openRoom();
startAudioAndVideoStream();
pushVideoStream();
stopAudioAndVideoStream();
closeRoom();
}
private void login() {
System.out.println("用户登录");
}
public abstract void openRoom(); // 打开房间
public abstract void startAudioAndVideoStream(); // 打开音视频流
public abstract void stopAudioAndVideoStream(); // 关闭音视频流
public abstract void closeRoom(); // 关闭房间
/* 父类默认不做任何事的方法称为钩子方法,方便子类重写 */
// 旁路推流
public void pushVideoStream() {
}
}
7.2 具体实现类
虎牙直播类:
public class HUYALivePlay extends LivePlay {
@Override
public void openRoom() {System.out.println("虎牙打开房间");}
@Override
public void startAudioAndVideoStream() {System.out.println("虎牙打开音视频流");}
@Override
public void stopAudioAndVideoStream() {System.out.println("虎牙关闭音视频流");}
@Override
public void closeRoom() {System.out.println("虎牙关闭房间");}
// 重写钩子方法,提供旁路推流功能
@Override
public void pushVideoStream() {
super.pushVideoStream();
System.out.println("虎牙进行旁路推流");
}
}
假设斗鱼直播类 DOUYULivePlay 没有旁路推流功能,就不用重写相应的钩子方法:
public class DOUYULivePlay extends LivePlay {
@Override
public void openRoom() {System.out.println("斗鱼打开房间");}
@Override
public void startAudioAndVideoStream() {System.out.println("斗鱼打开音视频流");}
@Override
public void stopAudioAndVideoStream() {System.out.println("斗鱼关闭音视频流");}
@Override
public void closeRoom() {System.out.println("斗鱼关闭房间");}
}
7.3 客户端调用
public class Client{
public static void main(String[] args) {
LivePlay hyLive = new HUYALivePlay();
hyLive.seeLivePlay();
System.out.println("--------");
LivePlay dyLive = new DOUYULivePlay();
dyLive.seeLivePlay();
}
}
第 8 章 策略模式
当写代码的时候发现一个操作有好多种实现方法,而需要根据不同的情况使用 if-else 等分支结构来确定使用哪种实现方式的时候,就使用策略模式。
举例,目前有三种出行方式的策略可以到达同一个目的地,使用何种策略取决于调用者(客户端)。
8.1 策略接口
public interface TravelStrategy {
int travelCosts(int distance);
}
8.2 封装各个算法
乘坐公交车算法:
public class ByBus implements TravelStrategy {
@Override
public int travelCosts(int distance) {
return distance < 10 ? 4 : 6;
}
}
乘坐滴滴算法:
public class ByDiDi implements TravelStrategy {
@Override
public int travelCosts(int distance) {
return distance < 3 ? 8 : (8 + (distance - 3) * 3);
}
}
骑共享单车算法:
public class BySharedBicycle implements TravelStrategy {
@Override
public int travelCosts(int distance) {
return 2;
}
}
8.3 使用算法
public class TravelCostCalculator {
public int goToDestination(TravelStrategy strategy, int distance){
return strategy.travelCosts(distance);
}
}
8.4 客户端调用
public class Client {
public static void main(String[] args) {
TravelCostCalculator calculator = new TravelCostCalculator();
System.out.println(String.format("乘坐公交车到目的地的花费为:%d块人民币",
calculator.goToDestination(new ByBus(), 10)));
System.out.println(String.format("乘坐滴滴到目的地的花费为:%d块人民币",
calculator.goToDestination(new ByDiDi(), 10)));
System.out.println(String.format("骑共享单车到目的地的花费为:%d块人民币",
calculator.goToDestination(new BySharedBicycle(), 10)));
}
}
第 9 章 建造者模式
当一个类的构造函数参数个数超过 4 个,而且这些参数有些是可选的参数,考虑使用建造者模式。建造者模式有以下四个组成:
- Product:最终要生成的具体产品对象,例如 Computer 实例。
- Builder:建造者的接口,定义了构建 Product 的抽象步骤,其中包含一个用来返回最终产品的方法 Product getProduct()
- ConcreteBuilder: Builder 接口的具体实现类
- Director: 指导者类,决定如何建造最终产品的算法。其会包含一个负责组装的方法 void Construct(Builder builder), 在这个方法中通过调用 builder 的方法,就可以设置 builder,等设置完成后,就可以通过 builder 的 getProduct() 方法获得最终的产品
9.1 目标产品 Product
目标产品的基类是 Computer:
public class Computer {
private String cpu; // 必要参数
private String ram; // 必要参数
private int usbCount; // 可选参数
private String keyboard; // 可选参数
private String display; // 可选参数
public Computer(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
public void setUsbCount(int usbCount) {this.usbCount = usbCount;}
public void setKeyboard(String keyboard) {this.keyboard = keyboard;}
public void setDisplay(String display) {this.display = display;}
@Override
public String toString() {
return "Computer{" +
"cpu='" + cpu + ''' +
", ram='" + ram + ''' +
", usbCount=" + usbCount +
", keyboard='" + keyboard + ''' +
", display='" + display + ''' +
'}';
}
}
9.2 接口
建造 Computer 的抽象接口:
interface ComputerBuilder {
void setUsbCount();
void setKeyboard();
void setDisplay();
Computer getComputer();
}
9.3 接口的具体实现类
苹果电脑实现类:
public class MacComputerBuilder implements ComputerBuilder {
private Computer computer;
public MacComputerBuilder(String cpu, String ram) {
computer = new Computer(cpu, ram);
}
@Override
public void setUsbCount() {
computer.setUsbCount(2);
}
@Override
public void setKeyboard() {
computer.setKeyboard("苹果键盘");
}
@Override
public void setDisplay() {
computer.setDisplay("苹果显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
联想电脑实现类:
public class LenovoComputerBuilder implements ComputerBuilder {
private Computer computer;
public LenovoComputerBuilder(String cpu, String ram) {
computer=new Computer(cpu,ram);
}
@Override
public void setUsbCount() {
computer.setUsbCount(3);
}
@Override
public void setKeyboard() {
computer.setKeyboard("联想键盘");
}
@Override
public void setDisplay() {
computer.setDisplay("联想显示器");
}
@Override
public Computer getComputer() {
return computer;
}
}
9.4 指导者类
public class ComputerDirector {
public void makeComputer(ComputerBuilder builder){
builder.setUsbCount();
builder.setDisplay();
builder.setKeyboard();
}
}
9.5 测试
public class Main {
public static void main(String[] args) {
ComputerDirector director = new ComputerDirector();
ComputerBuilder builder = new MacComputerBuilder("I5处理器", "三星125");
director.makeComputer(builder);
Computer macComputer = builder.getComputer();
System.out.println("mac computer:" + macComputer.toString());
ComputerBuilder lenovoBuilder = new LenovoComputerBuilder("I7处理器", "海力士222");
director.makeComputer(lenovoBuilder);
Computer lenovoComputer = lenovoBuilder.getComputer();
System.out.println("lenovo computer:" + lenovoComputer.toString());
}
}