设计模式
什么是设计模式
解决面向对象编程
- 设计模式( Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
学习设计模式的意义
- 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
- 正确使用设计模式具有以下优点
- 可以提高程序员的思维能力、编程能力和设计能力
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
设计模式的基本要素
- 模式名称
- 问题
- 解决方案
- 效果
GOF23
◆一种思维,一种态度,一种进步
- 创建型模式
- 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
- 结构型模式
- 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
- 行为型模式
- 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式,、策略模式、职责链模式、访冋者模式。
OOP七大原则
- 开闭原则:对扩展开放,对修改关闭。
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立。
- 依赖倒置原则:要面向接口编程,不要面向实现编程。
- 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性。
- 接口隔离原则:要为各个类建立它们需要的专用接囗。
- 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话。
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
创建型模式
**创建型模式**是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
单例模式
- 先构造
//饿汉式单例
public class Hungry {
/*可能会造成浪费空间,因为创建时,下面数组会被创建*/
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private byte[] data5 = new byte[1024*1024];
public Hungry() {
}
/*创建保证其唯一性*/
private final static Hungry hungry = new Hungry();
private static Hungry getInstance(){
return hungry;
}
}
//懒汉式单例
//构造器私有,用时再加载对象
public class LazyMan {
public LazyMan() {
System.out.println(Thread.currentThread().getName()+"ok");
}
private volatile static LazyMan lazyMan;
//一定要加volatile 实现真正的双重检测锁,防止指令重排
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
/*加锁*/
if (lazyMan == null){
synchronized (LazyMan.class){
if (lazyMan == null){
lazyMan = new LazyMan(); //不是一个原子性操作
}
}
}
return lazyMan;
}
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 例如期望执行 123(指的是上面的顺序)
* 但是真实执行 132
*
*
* 线程A进来执行的是132,
* 此时再来一个线程B判断lazyMan!=null
* LazyMan还没有完成构造
*/
//单线程下确实ok,
//多线程下,每次结果不一样,不稳定
public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
//静态内部类
public class Holder {
public Holder() {
}
public static Holder getInstace(){
return InnerClass.holder;
}
public static class InnerClass{
private static final Holder holder = new Holder();
}
}
拖得时间有些长 忘了看完了没
工厂模式
- 作用:
- 实现了创建者和调用者的分离
- 详细分类:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
- 满足的原则:
- 开闭原则: 一个软件的实体应当对扩展开放,对修改关闭。
- 依赖倒转原则:要针对接口编程,不要针对实现编程
- 迪米特法则:只与你直接的朋友通信,而避免和陌生人通信
- 核心本质:
- 实例化对象不使用new,用工厂方法代替
- 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
◆三种模式:
- 简单工厂模式
- 用来生产同-等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
- 工厂方法模式
- 用来生产同一等级结构中的固定产品(支持增加任意产品)
- 抽象工厂模式
- 围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
//结构复杂度:simple
//代码复杂度:simple
//编程复杂度:simple
//管理上的复杂度:simple//根据设计原则:工厂方法模式!
//根据实际业务:简单工厂模式!
静态工厂模式
// 静态工厂模式 --- 简单工厂模式
// 增加一款新产品必须修改代码,否则做不到。
// 开闭原则
public class CarFactory {
// 方法一
public static Car getCar(String car){
if (car.equals("五菱")){
return new Wuling();
}else if (car.equals("特斯拉")){
return new Tesla();
}else {
return null;
}
}
// 方法二
public static Car getWuling(){
return new Wuling();
}
public static Car getTesla(){
return new Tesla();
}
}
工厂方法模式
// 工厂方法模式
public interface CarFactory {
Car getCar();
}
public class TeslaFactory implements CarFactory{
@Override
public Car getCar() {
return new Tesla();
}
}
public class WulingFactory implements CarFactory{
@Override
public Car getCar() {
return new Wuling();
}
}
抽象工厂模式
- 定义:抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口, 无需指定它们具体的类
- 适用场景:
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 强调一系列相关的产品对象(属于同- -产品族)一起使用创建对象需要大量的重复代码
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现
- 优点:
- 具体产品在应用层的代码隔离,无需关心创建的细节
- 将一个系列的产品统一到一起创建
- 缺点:
- 规定了所有可能被创建的产品集合,产品簇中扩展新的产品困难;
- 增加了系统的抽象性和理解难度
建造者模式
- 建造者模式也属于创建型模式,它提供了一种创建对象的最佳方式。
- 定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 主要作用: 在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。
- 用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
- 例子:
- 工厂(建造者模式):负责制造汽车(组装过>程和细节在工厂内)
- 汽车购买者(用户):你只需要说出你需要的>型号(对象的类型和内容),然后直接购买就可以使用了(不需要知道汽车是怎么组装的(车轮、车门、>发动机、方向盘等等) )
应用场景: .
◆需要生成的产品对象有复杂的内部结构, 这些产品对象具备共性;
◆隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
◆适合于-个具有较多的零件(属性)的产品(对象)的创建过程。
◆建造者与抽象工厂模式的比较:
◆与抽象工厂模式相比,建造者模式返回-个组装好的完整产品,而抽象工厂模式返回一系列相关的产
品,这些产品位于不同的产品等级结构,构成了一个产品族。
◆在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,
客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装
过程和建造步骤,它侧重于一步步构造一 个复杂对象,返回- -个完整的对象。
◆>如果将抽象工厂模式看成汽车配件生产工厂,生产- -个产品族的产品,那么建造者模式就是一个汽车
组装工厂,通过对部件的组装可以返回一辆完整的汽车!
角色分析
- 上面示例是Builder模式的常规用法,导演类Director在Builder模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合。
- 通过静态内部类方式实现零件无序装配构造,这种方式使用更加灵活,更符合定义。内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品。
- 比如:比如麦当劳的套餐,服务员(具体建造者)可以随意搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交给用户来操作,使得产品的创建更加简单灵活。
- 优点:
- 产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节。
- 将复杂产品的创建步骤分 解在不同的方法中,使得创建过程更加清晰。
- 具体的建造者类之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库的代码,符合“开闭原则”
- 缺点:
- 建造者模式所创建的产品一般具有较多的共同点, 其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
- 应用场景:
- 需要生成的产品对象有复杂的内部结构, 这些产品对象具备共性;
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
- 适合于一个具有较多的零件(属性)的产品(对象)的创建过程。
- 建造者与抽象工厂模式的比较:
- 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
- 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。
- 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车!
原型模式
以某个东西为原型进行copy
[克隆]、Prototype、Cloneable接口、cloned方法
结构型模式
- 作用
- 从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。
- 分类
- 适配器模式
- 代理模式
- 桥接模式
- 装饰模式
- 组合模式
- 外观模式
- 享元模式
适配器模式
- 将一个类的接口转换成客户希望的另外一个接口。 Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
- 角色分析
- 目标接口:客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口。
- 需要适配的类:需要适配的类或适配者类。
- 适配器:通过包装一个需要适配的对象,把原接口转换成目标对象
- 对象适配器优点
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配
- 类适配器缺点
- 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者
- 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
- 适用场景
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
桥接模式
桥接模式是将抽象部分与它的实现部分分离,使他们都可以独立地变化。它是一种对象结构型模式,又称为接口模式或柄体模式。
树状违反了单一变量原则,比如想加一个商品就比较繁琐。拆分不够彻底。
这个场景中有两个变化的维度(品牌、类型)
实现后:
将品牌作为接口,新的品牌实现品牌接口;电脑作为类或者抽象类,实现保护类型默认电脑,新的类型电脑只需要继承电脑类。在客户选购时只需要选择心仪的品牌电脑款式。
- 好处分析
- 桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本。
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来!
- 劣势分析:
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别岀系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
- 最佳实践
- 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
- 场景
- Java语言通过Java虚拟机实现了平台的无关性。
- AWT中的Peer架构
- JDBC驱动程序也是桥接模式的应用之一。
代理模式
SpringAOP的底层 [SpringAOP和SpringMVC]
代理模式的分类:
- 静态代理
- 动态代理
静态代理模式
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,一般会做一些附属操作
- 客户:访问代理角色的人
package Dome;
/*静态代理模式总结:
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色*/
/*好处:
代理对象可以做很多真实对象做不了的事情
真实对象专注做自己的事仿*/
/**
* @author dafran
* @version 1.0
* @date 2020-05-23 20:07
*/
public class StaticProxy {
public static void main(String[] args) {
/* new Thread(()-> System.out.println("loveYou")).start();
new WeddingCompany(new You()).HappyMarry();*/
WeddingCompany weddingCompany = new WeddingCompany(new You());
weddingCompany.HappyMarry();
}
}
interface Marry {
void HappyMarry();
}
class You implements Marry {
@Override
public void HappyMarry() {
System.out.println("淦,老子结婚了");
}
}
class WeddingCompany implements Marry {
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before() {
System.out.println("布置婚礼现场");
}
private void after() {
System.out.println("收拾现场,收钱");
}
}
代码步骤:
- 接口
//租房
public interface Rent {
public void rent();
}
真实角色
//房东
public class Landlord implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
代理角色 ```java //代理 中介 public class Proxy implements Rent {
private Landlord landlord;
public Proxy() { }
public Proxy(Landlord landlord) {
this.landlord = landlord;
}
@Override
public void rent() {
seeHouse();
landlord.rent();
heTong();
fare();
}
//看房
public void seeHouse() {
System.out.println("中介带你看房");
}
//签署租赁合同
public void heTong() {
System.out.println("签署租赁合同");
}
//收取中介费
public void fare() {
System.out.println("收取中介费");
}
}
4. 客户端访问代理角色
```java
public class Tenant {
public static void main(String[] args) {
//房东要租房子
Landlord landlord = new Landlord();
//代理,中介帮房东租房,但是代理角色一般会有一些附属操作。
Proxy proxy = new Proxy(landlord);
//找中介租房即可
proxy.rent();
}
}
代理模式的好处
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共也就交给代理角色!实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
缺点:
- 真实角色就会产生—个代理角色;代码量会翻倍~开发效率会变低
加深理解
代码步骤:
- 接口
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
真实角色
//真实对象
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
代理角色 ```java public class UserServiceProxy implements UserService { private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override public void add() {
log("add");
userService.add();
}
@Override public void delete() {
log("delete");
userService.delete();
}
@Override public void update() {
log("update");
userService.update();
}
@Override public void query() {
log("query");
userService.query();
}
//日志方法
public void log(String msg) {
System.out.println("[Debug]使用了" + msg + "方法");
}
}
4. 客户端访问代理角色
```java
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
//使用代理对象代理
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
}
}
动态代理模式
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是直接写好的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口——JDK动态代理【在次举例】
- 基于类: cglib
- Java字节码实现: Javasist
需要了解两个类: Proxy:代理,Invocationhandler:调用处理程序
Invocationhandler
//将会自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//处理代理实例,并且返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName()); //通过反射获取方法名
//动态代理的本质就是使用反射机制实现
Object result = method.invoke(target, args);
return result;
}
public void log(String msg) {
System.out.println("执行了" + msg + "方法");
}
}
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//动态生成代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService);//设置要代理的对象
UserService proxy = (UserService) pih.getProxy();
proxy.add();
}
}
动态代理的好处
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共也就就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 动态代理类可以代理多个类,只要是实现了同一个接口即可