1、设计模式概述
1.是什么设计模式
设计模式(Design pattern) 是解决软件开发某些特定问题而提出的一些解决方案也可以理解成解决问题的一些思路。
通过设计模式可以帮助我们增强代码的可重用性、可扩充性、 可维护性、灵活性好。
高内聚,低耦合
主要解决OOP面向对象设计的一些问题
2.学习设计模式的意义
- 设计模式的本质是面向对象设计原则的充分运用,是对类的封装性,继承性,多态性以及类的关联关系和组合关系的充分理解
- 正确使用设局模式的优点
- 提高编程、思维、设计能力
- 是程序更加标准化,代码编制更加工程化,提高软件开发效率,缩短软件的开发周期
- 使代码重用性高,可读性强,可靠性高,灵活性好,可维护性强。
思想即业务,设计模式不一定最好,可能设计一个新的算法更好
3.设计模式的基本要素
- 设计模式
- 问题
- 解决方案
- 效果
GoF23
GoF23:一种思维,态度,进步
- 创建型模式:(主要对象的创建和使用)
- 单例模式,工厂模式,抽象工厂模式,建造者模式,原型模式
- 结构型模式:(类对象安装某种设计构成一种结构)
- 适配器模式,桥接,装饰,组合,外观,享元模式,代理模式
- 行为模式
- 模板方法模式。。。。太多没写完
oop七大原则
- 开闭原则:对扩展开放,对修改关闭
- 里氏替换原则:继承必须确保超类所拥有的性质在子类中仍然成立。(尽量增加新的功能,不要重写父类方法)
- 依赖倒置原则:要面向接口编程,不要面向实现编程
- 单一职责原则:控制类的粒度大小,将对象解耦,提高内聚性。(原子性:一个方法尽量专一)
- **接口隔离原则:**要为各个类建立它们需要的专用接口
- **迪米特法则:**只于你的直接朋友交谈,不和陌生人交谈
- **合成复用原则:**尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现
创建型模式
创建型模式关注的是“怎样创建对象”?
他的主要特点是:将对象的创建与使用分离!
种类
- 单例模式(Singleton):某类只生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型模式(Prototype):将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例
- 工厂模式(Factory Method):定义一个用于创建产品的接口,由子类决定生产什么产品!
- 抽象工厂模式(Abstra Factory):提供一个创建产品族的接口,每个子类可以选择生产一系列相关的产品。
- 建造者模式(Builder):将一个复杂对象分解为多个相对简单的部分,然后根据不同需要创建他们,最后组成该对象
1、工厂模式factory
作用:实现了创建者和调用者分离
简单分类:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
oop原则
- 开闭原则:一个软件的实体应当对扩展开放,对修改关闭
- 依赖倒转原则:面对接口编程
- 迪米特法则:只与朋友通信
核心本质
- 实例化对象不用new,使用工厂方法代替
- 将选择实现类,创建对象统一管理和控制,从而将调用者和我们的实现类解耦。
介绍
**意图:**定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
**主要解决:**主要解决接口选择的问题。
**何时使用:**我们明确地计划不同条件下创建不同实例时。
**如何解决:**让其子类实现工厂接口,返回的也是一个抽象的产品。
**关键代码:**创建过程在其子类执行。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
**缺点:**每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
使用场景: 1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。
**注意事项:**作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
三种模式
简单工厂模式
-
简单工厂模式
- 用来生产同一等级结构中的任意产品(对于新增加的产品,覆盖已有的代码)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uG243zNe-1651654148916)(23种设计模式/image-20210520172613075.png)]
package com.kuang.factory.simpleFactory; //静态工厂模式,简单工厂模式 //增加一个新的功能,如果不修改代码,做不到 public class CarFactory { /* 假如我们想要增加一种车,直接修改不好,违背了开闭原则 */ // 方法一 public static Car getCar(String car){ if (car.equals("五菱宏光")){ return new WuLeng(); }else if (car.equals("Tesla")){ return new Tesla(); }else { return null; } } // 方法二 public static Car getWuLeng(){ return new WuLeng(); } public static Car getTesla(){ return new Tesla(); } }
-
工厂方法模式
- 用来生产同一等级结构中的固定产品,(支持增加任意产品)
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SEBFTUZA-1651654148918)(23种设计模式/image-20210520172902148.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HXtHCuVs-1651654148918)(23种设计模式/image-20210520173043077.png)]
工厂方法模式
package com.kuang.factory.method;
public class consumer {
public static void main(String[] args) {
Car car = new WuLengFactory().getCar();
Car car2 = new TeslaFactory().getCar();
Car car1 = new MobiFactory().getCar();
car.name();
car2.name();
car1.name();
}
}
抽象工厂方法
- 围绕一个超级工厂创建其他工厂,该超级工厂又被称为其他工厂的工厂。
介绍
**意图:**提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
**主要解决:**主要解决接口选择的问题。
**何时使用:**系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
**如何解决:**在一个产品族里面,定义多个产品。
**关键代码:**在一个工厂里聚合多个同类产品。
**应用实例:**工作了,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装(成套,一系列具体产品)、时尚装(成套,一系列具体产品),甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。假设一种情况(现实中是不存在的,要不然,没法进入共产主义了,但有利于说明抽象工厂模式),在您的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用 OOP 的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。
**优点:**当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
**缺点:**产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
使用场景: 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。
**注意事项:**产品族难扩展,产品等级易扩展。
2.单例模式
核心思想
通常普通类的构造方法是公有的,外部类可以通过“new 构造函数()”来生成多个实例。
但是将类的构造函数设为私有,外部类就无法调用。
定义一个静态私有实例,并向外提供一个静态共有函数,用于获取该实例!
饿汉式
饿汉式
package com.kuang.single;
//饿汉式
/*
构造器私有!
*/
public class Hungry {
//可能会造成浪费
private Hungry(){}
private static final Hungry HUNGRY=new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
DCL懒汉式
DCL懒汉式
package com.kuang.single;
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}//私有化构造器方法,防止外部类多次实例化它
private volatile static LazyMan lazyMan;//自己先实例化一个懒汉实例
// 双重检测锁模式的懒汉式单例》》DCL懒汉式
public static LazyMan getInstance(){//静态方法,可以直接类名.方法使用
// 加锁
if(lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan=new LazyMan();//不是原子性操作
}
}
}
return lazyMan;
}
/*
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
123 A
132 B 此时lazyMan还没有构造,所以要加volatile
*/
//单线程下ok
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
静态内部类
静态内部类
package com.kuang.single;
//静态内部类
public class Holder {
private Holder(){}//1.构造器私有
public static Holder getInstance(){
//3.调用内部类的实例
return InnerClass.HOLDER;
}
public static class InnerClass{
//2.创建对象
private static final Holder HOLDER=new Holder();
}
}
反射可以破发内部类
package com.kuang.single;
import java.lang.reflect.Constructor;
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}//私有化构造器方法,防止外部类多次实例化它
private volatile static LazyMan lazyMan;//自己先实例化一个懒汉实例
// 双重检测锁模式的懒汉式单例》》DCL懒汉式
public static LazyMan getInstance(){//静态方法,可以直接类名.方法使用
// 加锁
if(lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan=new LazyMan();//不是原子性操作
}
}
}
return lazyMan;
}
/*
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
123 A
132 B 此时lazyMan还没有构造,所以要加volatile
*/
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);//反射可以破环单例
}
}
解决1(三重检测)
(三重检测)
package com.kuang.single;
import java.lang.reflect.Constructor;
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要试图使用反射破坏单例");
}
}
}//私有化构造器方法,防止外部类多次实例化它
private volatile static LazyMan lazyMan;//自己先实例化一个懒汉实例
// 双重检测锁模式的懒汉式单例》》DCL懒汉式
public static LazyMan getInstance(){//静态方法,可以直接类名.方法使用
// 加锁
if(lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan=new LazyMan();//不是原子性操作
}
}
}
return lazyMan;
}
/*
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
123 A
132 B 此时lazyMan还没有构造,所以要加volatile
*/
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);//反射可以破环单例
}
}
但是当直接使用反射创建对象时,还是会出现问题
public static void main(String[] args) throws Exception {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);//反射可以破环单例
}
解决:
package com.kuang.single;
import java.lang.reflect.Constructor;
public class LazyMan {
private static boolean qingjian=false;
private LazyMan(){
synchronized (LazyMan.class){
if (qingjian==false){
qingjian=true;
}else {
throw new RuntimeException("不要试图使用反射破坏单例");
}
}
}//私有化构造器方法,防止外部类多次实例化它
private volatile static LazyMan lazyMan;//自己先实例化一个懒汉实例
// 双重检测锁模式的懒汉式单例》》DCL懒汉式
public static LazyMan getInstance(){//静态方法,可以直接类名.方法使用
// 加锁
if(lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan=new LazyMan();//不是原子性操作
}
}
}
return lazyMan;
}
/*
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
123 A
132 B 此时lazyMan还没有构造,所以要加volatile
*/
public static void main(String[] args) throws Exception {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);//反射可以破环单例
}
}
道高一尺,魔高一丈
public static void main(String[] args) throws Exception {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
Field qingjian = LazyMan.class.getDeclaredField("qingjian");
qingjian.setAccessible(true);
qingjian.set(instance,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);//反射可以破环单例
}
反射不能破坏枚举类型的单例
package com.kuang.single;
import javax.print.DocFlavor;
import java.lang.ref.PhantomReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//枚举是一个什么?,枚举本身也是一个class类
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingleton instance1=EnumSingleton.INSTANCE;
EnumSingleton instance2=EnumSingleton.INSTANCE;
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingleton enumSingleton = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(enumSingleton);
// Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
}
}
jdc反编译工具!!!
3、原型模式
定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或者类似的新对象!
2、模式结构:
- 抽象原型类:规定了具体原型类必须实现的接口
- 具体原型类:实现抽象原型类的clone()方法,可复制
- 访问类:使用具体原型类中clone()方法来复制的对象!!
3、实现:
由于java中提供了对象的clone方法,所以java实现原型模式是很简单的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r0myNGXE-1651654148919)(23种设计模式/image-20211023091751825.png)]
这里面的抽象原型类是Cloneable接口,可以实现浅拷贝,具体原型类只要实现接口实现对象的浅拷贝。!!
4、测试代码如下
package com.chen.PrototypePattern.example1;
class Realizetype implements Cloneable{
//Realizetype具体原型类,实现了clone方法, Cloneable抽象原型类
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("具体原型类复制成功!!");
return super.clone();
}
//无参构造
Realizetype(){
System.out.println("具体原型类创建成功!");
}
}
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype obj1=new Realizetype();
Realizetype obj2= (Realizetype) obj1.clone();
System.out.println("obj1==obj2?"+(obj1==obj2));
}
}
扩展:
原型模式可以扩展为带原型管理器的原型模式,他在原型模式的基础上增加了一个原型管理器[PrototypeManager类].
该类使用HashMap保存多个复制的原型!,Client类可以通过管理器的get方法获得原型类的复制!!
5、总结
原型类的目的是为了创建重复的对象,同时又能保证性能。
4、建造者模式(Builder)
软件开发过程中有时需要创建一个复杂对象,这个复杂对象通常由多个子部件按一定步骤组合而成!
4.1、建造者的定义
将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的对象。
4·2、介绍
**意图:**将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
**主要解决:**主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
**何时使用:**一些基本部件不会变,而其组合经常变化的时候。
**如何解决:**将变与不变分离开。
**关键代码:**建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
**注意事项:**与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
4·3、实现
我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。
我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。
然后我们创建一个 Meal 类,带有 Item 的 ArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilder。BuilderPatternDemo 类使用 MealBuilder 来创建一个 Meal。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YWQsOImb-1651654148920)(23种设计模式/image-20211023153449614.png)]
测试代码
步骤 1
创建一个表示食物条目和食物包装的接口。
public interface Item {
public String name();
public Packing packing();
public float price();
}
public interface Packing {
public String pack();
}
步骤 2
创建实现 Packing 接口的实体类。
public class Wrapper implements Packing {
@Override
public String pack() {
return "Wrapper";
}
}
public class Bottle implements Packing {
@Override
public String pack() {
return "Bottle";
}
}
步骤 3
创建实现 Item 接口的抽象类,该类提供了默认的功能。
public abstract class Burger implements Item {
@Override
public Packing packing() {
return new Wrapper();
}
@Override
public abstract float price();
}
public abstract class ColdDrink implements Item {
@Override
public Packing packing() {
return new Bottle();
}
@Override
public abstract float price();
}
步骤 4
创建扩展了 Burger 和 ColdDrink 的实体类。
public class VegBurger extends Burger {
@Override
public float price() {
return 25.0f;
}
@Override
public String name() {
return "Veg Burger";
}
}
public class ChickenBurger extends Burger {
@Override
public float price() {
return 50.5f;
}
@Override
public String name() {
return "Chicken Burger";
}
}
public class Coke extends ColdDrink {
@Override
public float price() {
return 30.0f;
}
@Override
public String name() {
return "Coke";
}
}
public class Pepsi extends ColdDrink {
@Override
public float price() {
return 35.0f;
}
@Override
public String name() {
return "Pepsi";
}
}
步骤 5
创建一个 Meal 类,带有上面定义的 Item 对象。
import java.util.ArrayList;
import java.util.List;
public class Meal {
private List<Item> items = new ArrayList<Item>();
public void addItem(Item item){
items.add(item);
}
public float getCost(){
float cost = 0.0f;
for (Item item : items) {
cost += item.price();
}
return cost;
}
public void showItems(){
for (Item item : items) {
System.out.print("Item : "+item.name());
System.out.print(", Packing : "+item.packing().pack());
System.out.println(", Price : "+item.price());
}
}
}
步骤 6
创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象。
public class MealBuilder {
public Meal prepareVegMeal (){
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
public Meal prepareNonVegMeal (){
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}
步骤 7
BuiderPatternDemo 使用 MealBuilder 来演示建造者模式(Builder Pattern)。
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();
Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total Cost: " +vegMeal.getCost());
Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost: " +nonVegMeal.getCost());
}
}
步骤 8
执行程序,输出结果:
Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0
Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.
结构型模式
结构模型概述:
结构模型描述如何将类或者对象按某种布局组成更大的结构,有类结构型模式和对象结构模式,前者使用继承机制,后者使用组合或聚合来组合对象!
结构型模式分为以下七种:
- **代理模式(**Proxy):为某对象提供一种代理以控制对该对象的访问,即客户端通过代理间接的访问该该对象。
- 适配器模式(Adapter):将一个类的接口转换为客户希望的另一个接口。
- 桥接模式(Bridge):将抽象和实现分离,是他们可以独立变化!
- 装饰模式(Decorator):动态的给对象增加一些职责!
- 外观模式(Facade):为多个复杂的子系统提供一个一致的接口,使这些子系统更容易访问
- 享元模式(Flyweight):运用共享技术来有效的支持大量细粒度对象的复用
- 组合模式(Composite):将对象组合成树状层次结构,使用户对单个对象和组合对象都有一致的访问性。
代理模式
介绍
**意图:**为其他对象提供一种代理以控制对这个对象的访问。
**主要解决:**在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
**何时使用:**想在访问一个类时做一些控制。
**如何解决:**增加中间层。
**关键代码:**实现与被代理类组合。
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
**使用场景:**按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
spring的动态代理技术
Spring AOP 是通过动态代理技术实现的,而动态代理是基于反射设计的,对于反射基础,属于 Java 基础,这里就不再多说了,动态代理技术的实现方式有两种:
1、基于接口的 JDK 动态代理
2、基于继承的 CGLib 动态代理
适配器模式(Adapter)
介绍
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
我们通过下面的实例来演示适配器模式的使用。其中,音频播放器设备只能播放 mp3 文件,通过使用一个更高级的音频播放器来播放 vlc 和 mp4 文件。
**意图:**将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
**主要解决:**主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
**如何解决:**继承或依赖(推荐)。
**关键代码:**适配器继承或依赖已有的对象,实现想要的目标接口。
应用实例: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
**使用场景:**有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
**注意事项:**适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
桥接模式(Bridge)
将抽象和实现分离,是他们可以独立变化!
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
我们通过下面的实例来演示桥接模式(Bridge Pattern)的用法。其中,可以使用相同的抽象类方法但是不同的桥接实现类,来画出不同颜色的圆。
介绍
**意图:**将抽象部分与实现部分分离,使它们都可以独立的变化。
**主要解决:**在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
**何时使用:**实现系统可能有多个角度分类,每一种角度都可能变化。
**如何解决:**把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
**关键代码:**抽象类依赖实现类。
应用实例: 1、猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。 2、墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。
优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
**缺点:**桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
使用场景: 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。 2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
**注意事项:**对于两个独立变化的维度,使用桥接模式再适合不过了。
实现
我们有一个作为桥接实现的 DrawAPI 接口和实现了 DrawAPI 接口的实体类 RedCircle、GreenCircle。Shape 是一个抽象类,将使用 DrawAPI 的对象。BridgePatternDemo 类使用 Shape 类来画出不同颜色的圆。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RHtzvE9-1651654148921)(23种设计模式/image-20211023201618269.png)]
- 装饰模式(Decorator):
动态的给对象增加一些职责!
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
介绍
**意图:**动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
**主要解决:**一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
**何时使用:**在不想增加很多子类的情况下扩展类。
**如何解决:**将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
**优点:**装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
**缺点:**多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
**注意事项:**可代替继承。
外观模式(Facade):
- 为多个复杂的子系统提供一个一致的接口,使这些子系统更容易访问
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
介绍
**意图:**为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
**主要解决:**降低访问复杂系统的内部子系统时的复杂度,简化客户端之间的接口。
何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。
**如何解决:**客户端不与系统耦合,外观类与系统耦合。
**关键代码:**在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。
应用实例: 1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。 2、JAVA 的三层开发模式。
优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。
**缺点:**不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。
**注意事项:**在层次化结构中,可以使用外观模式定义系统中每一层的入口
享元模式(Flyweight):
运用共享技术来有效的支持大量细粒度对象的复用
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。我们将通过创建 5 个对象来画出 20 个分布于不同位置的圆来演示这种模式。由于只有 5 种可用的颜色,所以 color 属性被用来检查现有的 Circle 对象。
介绍
**意图:**运用共享技术有效地支持大量细粒度的对象。
**主要解决:**在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。
**如何解决:**用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
**关键代码:**用 HashMap 存储这些对象。
应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。
**优点:**大大减少对象的创建,降低系统的内存,使效率提高。
**缺点:**提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。
注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。
组合模式(Composite)
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
我们通过下面的实例来演示组合模式的用法。实例演示了一个组织中员工的层次结构。
介绍
**意图:**将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
**主要解决:**它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
**如何解决:**树枝和叶子实现统一接口,树枝内部组合该接口。
**关键代码:**树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作数也可以是操作数、操作符和另一个操作数。 2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。
优点: 1、高层模块调用简单。 2、节点自由增加。
**缺点:**在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
**使用场景:**部分、整体场景,如树形菜单,文件、文件夹的管理。
**注意事项:**定义时为具体类。
赖于这些对象身份,这些对象是不可分辨的。
**如何解决:**用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。
**关键代码:**用 HashMap 存储这些对象。
应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。
**优点:**大大减少对象的创建,降低系统的内存,使效率提高。
**缺点:**提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。
注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。
组合模式(Composite)
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。
这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。
我们通过下面的实例来演示组合模式的用法。实例演示了一个组织中员工的层次结构。
介绍
**意图:**将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
**主要解决:**它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
**如何解决:**树枝和叶子实现统一接口,树枝内部组合该接口。
**关键代码:**树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作数也可以是操作数、操作符和另一个操作数。 2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。
优点: 1、高层模块调用简单。 2、节点自由增加。
**缺点:**在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
**使用场景:**部分、整体场景,如树形菜单,文件、文件夹的管理。
**注意事项:**定义时为具体类。
未完待续