创建型

单例模式

饿汉式(在使用时进行实例化,线程不安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class achieve1 {

private static achieve1 singleton ;
// 私有化构造器 禁止外部构造 不能防止反射破环
private achieve1(){
}
// 给外界提供获取实例方法 线程不安全
// 不安全的原因:同时多个线程去构造实例
public static achieve1 getInstance(){
if(singleton == null) {
singleton = new achieve1();
}
return singleton;
}
}

懒汉模式要变成线程安全的除了用饿汉模式之外,还有两种方法:

  1. synchronized关键字

此方法是最简单又有效的方法,不过对性能上会有所损失。比如两个线程同时调用这个实例,其中一个线程要等另一个线程调用完才可以继续调用。而线程不安全往往发生在这个实例在第一次调用的时候发生,当实例被调用一次后,线程是安全的,所以加synchronized就显得有些浪费性能。

1
2
3
4
5
6
public static synchronized achieve1 getInstance(){
if(singleton == null) {
singleton = new achieve1();
}
return singleton;
}
  1. 双重检查加锁

线程不安全往往发生在这个实例在第一次调用的时候发生,当实例被调用一次后,线程是安全的。那有没有方法只有在第一次调用的时候才用synchronized关键字,而第一次后就不用synchronized关键字呢?答案是当然有的,就是用volatile来修饰静态变量,保持其可见性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class achieveVolatile {
private static volatile achieveVolatile singleton;
private achieveVolatile() {
}

public synchronized static achieveVolatile getInstance() {
if (singleton == null) {
synchronized (achieveVolatile.class) {
if (singleton == null) singleton = new achieveVolatile();
}
}
return singleton;
}
}

饿汉式(在创建时就初始化完成,线程安全)

1
2
3
4
5
6
7
8
public class achieve2 {

private static achieve2 singleton = new achieve2();
private achieve2(){}
public achieve2 getInstance(){
return singleton;
}
}

使用 CAS 实现线程安全的单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AchieveByCAS {
private static final AtomicReference<AchieveByCAS> INSTANCE = new AtomicReference<AchieveByCAS>();

/**
* 单例核心 私有化构造器
*/
private AchieveByCAS(){}

public static AchieveByCAS getInstance(){
for (;;) {
AchieveByCAS achieveByCAS = INSTANCE.get();
if(achieveByCAS != null) return achieveByCAS;

achieveByCAS = new AchieveByCAS();
if(INSTANCE.compareAndSet(null,achieveByCAS)){
return achieveByCAS;
}
}
}
}

工厂模式

意图:

工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。

简单工厂(不影响其他代码的情况下扩展产品创建部分代码。)

定制生产交通工具的流程, 应该怎么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface Factory {
// 统一的创建
void create();
}
// 子类实现
public class Car implements Factory{
@Override
public void create() {
System.out.println("生产车。。。。");
}
}
public class plane implements Factory{
@Override
public void create() {
System.out.println("生产飞机。。。。");
}
}
// 调用
public static void main(String[] args) {
Factory car = new Car();
Factory plane = new plane();
car.create();
plane.create();
}

抽象工厂(不影响其他代码的情况下扩展产品创建部分代码。)

我们需要一种交通工具以到达某个城市外, 可能还需要一把AK47, 并且还需要一个苹果以备路上不时之需.

  • 所以我们需要给他一个工厂来制造这一系列产品.

  • 为了提高可扩展性, 我们还希望不同的工厂可以制作不同系列的产品, 比如上面说的A工厂制造的是汽车, AK47, 苹果; 而B工厂制造的是飞机, 火箭炮, 旺仔小馒头.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public abstract class Vehiche { // 交通工具的抽象类
abstract void run();
}
public abstract class Weapon { // 武器的抽象类
abstract void fire();
}
public abstract class Food { // 食物的抽象类
abstract void eat();
}
public interface Factory { // 抽象工厂 提供多种产品的实现
Vehiche createVehiche();
Weapon createWeapon();
Food createFood();
}
public class Car extends Vehiche{ // 汽车产品的实现
@Override
void run() {
System.out.println("小汽车启动...");
}
}
public class AK47 extends Weapon{ // 武器产品的实现
@Override
void fire() {
System.out.println("哒哒哒...");
}
}
public class Apple extends Food{ // 食物的实现
@Override
void eat() {
System.out.println("吃苹果。。。");
}
}
public class FactoryImpl implements Factory{ // 不同产品对应工厂的不同实现
@Override
public Vehiche createVehiche() {
return new Car();
}
@Override
public Weapon createWeapon() {
return new AK47();
}
@Override
public Food createFood() {
return new Apple();
}
}
// 测试
public static void main(String[] args) {
FactoryImpl factory = new FactoryImpl();
Vehiche vehiche = factory.createVehiche();
Weapon weapon = factory.createWeapon();
Food food = factory.createFood();

vehiche.run();
weapon.fire();
food.eat();
}
  • 总结一下, 抽象工厂和简单工厂各有什么优劣?
  • 抽象工厂能够生产一系列产品, 也能方便地替换掉一系列产品, 但是如果想要在产品系列中添加多一个品种将会非常麻烦. 比如说在上面的系列产品中添加一个盔甲抽象类, 那么抽象工厂以及对应的实现都要修改源码了.
  • 而简单工厂能够灵活的生产但一个品种的产品, 但是如果生产的品种较多, 会出现工厂泛滥的问题.
  • 两者优劣互补, 那么有没有可以兼容两者优点的工厂实现呢? 下面看 spring的工厂实现, 它给出了一种解决方案.

Spring的Bean工厂

模拟 Spring工厂实现

模拟 IOC:

  • 都说 Spring是个 bean容器, 以下的代码将展示它是如何生成 bean, 并把 bean放入容器中供用户获取的.
  • 思路比较简单:
  1. 创建 BeanFactory工厂接口, 添加方法 getBean().
  2. 创建 BeanFactory的实现类 ClassPathXmlApplicationContext. 将在该实现类中展示 IOC的具体实现.
  3. ClassPathXmlApplicationContext需要一个 container容器存放创建的 bean对象, 这里使用 HashMap实现.
  4. ClassPathXmlApplicationContext的构造方法中读取 spring的配置文件, 这里使用到了 dom4j. 读取配置文件后根据 beanclass属性使用反射创建出 bean对象. 然后把 idbean对象分别作为 keyvalue添加到容器中.
  5. 当工厂被调用 getBean()方法时, 从容器中找到对应的 bean并返回.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public interface BeanFactory { //极简 BeanFactory
Object getBean(String beanId);
}
// BeanFactory的实现类
public class ClassPathXmlApplicationContext implements BeanFactory {

private Map<String, Object> BeanDefinition = new HashMap<>();

//通过构造方法读取配置文件
public ClassPathXmlApplicationContext(String proper) {
SAXReader reader = new SAXReader();
File file = new File(this.getClass()
.getClassLoader()
.getResource(proper)
.toURI());
Document document = reader.read(file);
Element root = document.getRootElement();

List<Element> childElements = root.elements();

for (Element child : childElements) {
Object bean = Class.forName(child.attributeValue("class")).newInstance();
BeanDefinition.put(child.attributeValue("id"), bean);
}
}
@Override
public Object getBean(String beanId) {
return BeanDefinition.containsKey(beanId) ? BeanDefinition.get(beanId) : null;
}
}
//xml中配置的 bean
<bean id="v"class="designPattern.factory.Car"></bean>
// 测试
public static void main(String[] args) {
BeanFactory run = new ClassPathXmlApplicationContext("application.xml");
Vehiche v = (Vehiche) run.getBean("v");
v.run();
}

原型模式

原型模式是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类。

原型模式通常包括两个角色:原型类和具体原型类。

  1. 原型类是一个抽象的类或接口,声明了用于复制自己的方法。
  2. 具体原型类是具体的实现类,在实现父类(或接口)中定义的复制方法时,需要注意实现深拷贝和浅拷贝,以确保复制出来的对象完全符合预期。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class PersonalInfo {
protected String name;
protected String sex;
protected String age;
。。。。
// 省略构造和get set
}
public class Resume implements Cloneable {

private PersonalInfo personalInfo;
private String company;
private String workTime;
。。。。
// 省略构造和get set
/**
* 实现clone方法
*/
public Resume clone() {
Resume object = null;
// 使用克隆对象进行克隆内容
try {
object = (Resume) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("克隆异常了");
throw new RuntimeException(e);
}
return object;
}
}
// 测试
public static void main(String[] args) {
PersonalInfo personal = new PersonalInfo("Shier", "男", "19");
Resume resume1 = new Resume();
resume1.SetPersonalInfo(personal);
resume1.setWorkExperience("琴酒1", "2023-04~05");
// 使用resume1进行调用clone对象
Resume resume2 = resume1.clone();
personal.setName("panther");
resume2.setWorkExperience("琴酒2", "2023-04~06");

Resume resume3 = resume1.clone();
resume3.setWorkExperience("琴酒3", "2023-04~07");

resume1.showResume();
resume2.showResume();
resume3.showResume();
}
// 结果 姓名都被覆盖了
姓名:panther 年龄19 性别男
工作经历:琴酒1 时间:2023-04~05
姓名:panther 年龄19 性别男
工作经历:琴酒2 时间:2023-04~06
姓名:panther 年龄19 性别男
工作经历:琴酒3 时间:2023-04~07

现在’简历’对象里的数据存在是String型的,而String是一种拥有值类型特点的特殊引用类型,super.clone()方法是这样,如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其副本引用同一对象。什么意思呢?就是说如果你的’简历’类当中有对象引用,那么引用的对象数据是不会被克隆过来的。

扩展深拷贝浅拷贝

  1. 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。但我们可能更需要这样的一种需求,把要复制的对象所引用的对象都复制一遍
  2. 深拷贝:是指创建一个新对象,并将原始对象中的所有非静态字段及其关联对象的值复制到新对象中。如果字段是基本数据类型,则拷贝它们的值;如果字段是引用类型,则递归地拷贝它们所指向的对象,直到所有引用对象都被拷贝为止。因此,原始对象和副本对象将不共享任何对象。

改进程序(将personalInfo类也实现Cloneable接口添加克隆方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// personalInfo类也实现Cloneable
public class PersonalInfo implements Cloneable{
protected String name;
protected String sex;
protected String age;
// 添加克隆代码
public PersonalInfo clone() {
PersonalInfo object = null;
// 使用克隆对象进行克隆内容
try {
object = (PersonalInfo) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("克隆异常了");
throw new RuntimeException(e);
}
return object;
}
}
// 添加深拷贝代码
public Resume clone() {
Resume object = null;
// 使用克隆对象进行克隆内容
try {
object = (Resume) super.clone();
//深拷贝代码
this.personalInfo = this.personalInfo.clone();
} catch (CloneNotSupportedException e) {
System.out.println("克隆异常了");
throw new RuntimeException(e);
}
return object;
}
// 测试
姓名:Shier 年龄19 性别男
工作经历:琴酒1 时间:2023-04~05
姓名:panther 年龄19 性别男
工作经历:琴酒2 时间:2023-04~06
姓名:Shier 年龄19 性别男
工作经历:琴酒3 时间:2023-04~07

原型模式优点:

  1. 可以在不编写创建代码的情况下创建新对象。
  2. 可以减少代码重复,因为我们可以通过拷贝现有对象来避免多次编写相同的创建代码。
  3. 可以减少初始化操作或构造函数,并使代码更加灵活和可扩展。

原型模式缺点:

  1. 如果拷贝操作很复杂,可能会导致性能问题。
  2. 如果对象有循环依赖关系,则需要特殊处理。

结构型

适配器模式

适配器模式(Adapter Pattern),它将一个类的接口转换成客户端所期望的另一种接口,让原本不兼容的接口可以在一起工作。适配器模式常被用于将旧的代码和新的代码无缝地集成在一起,从而减少系统重构的成本。

对象适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 提供给客户的接口
public class Target {
public void request() {
System.out.println("客户普通请求!!");
}
}
// 需要适配的特殊接口
public class adaptee {
public void specificRequest(){
System.out.println("需要适配的特殊请求");
}
}
// 新建一个适配器适配特殊接口
public class Adapte extends Target{
private adaptee adaptee = new adaptee();
/**
* 适配的请求
*/
public void request(){
adaptee.specificRequest();
}
}
// test
public static void main(String[] args) {
Target target1 = new Target();
Target target2 = new Adapte();
target1.request(); // 客户普通请求!!
target2.request(); // 需要适配的特殊请求
}

适配器模式的优点

  1. 解决接口不兼容性:适配器模式可以帮助解决不同类之间接口不兼容的问题,使得原本无法协同工作的类能够一起工作。
  2. 可复用性:适配器模式可以复用现有的类,而无需修改其代码结构。通过适配器,可以使得已经存在的类能够适应新的接口。
  3. 系统扩展性:当需要引入新的类并与现有类协同工作时,适配器模式可以提供一种灵活的方式,而无需修改现有类的代码。
  4. 客户代码可以统一调用同一接口,让程序更简单、更直接、更紧凑。

适配器模式的缺点

  1. 是需要增加额外的代码来完成适配器的实现,从而增加了系统的复杂度。因此,在确定使用适配器模式时,需要权衡其优点和缺点,选择最合适的实现方式。
  2. 增加了复杂性:引入适配器模式会增加代码的复杂性,特别是当存在多个适配器时,可能会导致代码更难理解和维护。
  3. 运行时性能损耗:由于适配器需要进行接口转换和数据处理,可能会导致一定的运行时性能损耗。

组合模式

装饰器模式

代理模式

模式结构

设计模式之代理模式

Subject(抽象主题角色) Proxy(代理主题角色) RealSubject(真实主题角色)

在代理模式中,通常会涉及到三个角色:抽象主题角色、真实主题角色和代理主题角色。

  1. 抽象主题角色声明了真实主题角色和代理主题角色的公共方法
  2. 真实主题角色实现了抽象主题角色定义的接口,代表着真实的业务实现
  3. 代理主题角色持有真实主题角色的引用,可以访问真实主题角色,同时还可以在真实主题角色基础上添加一些附加的操作

模拟小王给小红送花

  • 小王和小红
1
2
3
4
5
6
7
8
public class xiaowang {
private String name;
private int age;
}
public class xiaohong {
private String name;
private int age;
}
  • 但是小王没有胆量直接送花所以联系了花店(提供送花服务)
1
2
3
4
5
public class flowerShop {
public void giveFlower(String buyName , String sellName){
System.out.println(buyName + "给"+sellName+"送花");
}
}
  • 但是小王还想添加一些创意,不想只是送花
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class flowerShopProxy {

private flowerShop flowerShop = new flowerShop();

private void preProcess(){
// 前置操作
}

public void giveFlower(String buyName , String sellName){
preProcess();
flowerShop.giveFlower(buyName,sellName);
postProcess();
}

private void postProcess(){
// 后置操作
}

}

代理模式的优点:

  • 职责清晰
  • 高扩展性
  • 智能化

常见代理模式的应用

  1. 远程代理,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实
  2. 虚拟代理,是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象
  3. 安全代理,用来控制真实对象访问时的权限
  4. 智能指引,是指当调用真实的对象时,代理处理另外一些事

享元模式

享元模式是一种结构型设计模式,旨在通过共享对象来减少内存使用和提高性能。它主要用于处理大量细粒度对象的情况,其中许多对象具有相似的属性和行为。

在享元模式中,对象分为两种类型(可共享的内部状态和不可共享的外部状态):

内部状态(Intrinsic State)外部状态(Extrinsic State)

  1. 内部状态是对象的固有属性,它们不随外部环境的改变而改变。
  2. 外部状态取决于外部环境,它们在运行时可以改变。

享元模式的核心思想是将具有相同内部状态的对象共享,以减少内存占用。当需要创建一个对象时,首先检查是否已经存在具有相同内部状态的对象。如果存在,则重用该对象,而不是创建一个新的对象。如果不存在,则创建一个新的对象并将其添加到共享池中,以供以后使用。典型的享元类代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.HashMap;
import java.util.Map;

// 网站接口
interface Website {
void use();
}

// 具体网站类
class ConcreteWebsite implements Website {
private String type;

public ConcreteWebsite(String type) {
this.type = type;
}

public void use() {
System.out.println("使用 " + type + " 网站");
}
}

// 网站工厂
class WebsiteFactory {
private Map<String, Website> websites = new HashMap<>();

public Website getWebsite(String type) {
if (!websites.containsKey(type)) {
websites.put(type, new ConcreteWebsite(type));
}
return websites.get(type);
}

public int getWebsiteCount() {
return websites.size();
}
}

public class FlyweightPatternDemo {
public static void main(String[] args) {
WebsiteFactory factory = new WebsiteFactory();

Website blog = factory.getWebsite("博客");
blog.use();

Website forum = factory.getWebsite("论坛");
forum.use();

Website blog2 = factory.getWebsite("博客");
blog2.use();

System.out.println("网站数量:" + factory.getWebsiteCount());
}
}

外观模式(门面模式)

意图:能为子系统、框架或其他复杂类提供一个简单的接口。(类比淘宝,你只需要下单付款,不需要了解内部实现,淘宝内部经过一些列的处理最终将货物送到你手里)从而使子系统更易用、更易懂、更易扩展。

外观模式的核心思想是封装,即将一组复杂的类或接口封装在一个外观类中,客户端只与外观类进行交互,而不直接与复杂的子系统进行交互。外观类隐藏了子系统的复杂性,客户端只需要知道如何使用外观类提供的接口即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 实现三个子系统
public class childOne {
public void SystemOne(){System.out.println("第一个子系统");}
}
public class childTwo {
public void SystemTwo(){System.out.println("第二个子系统");}
}
public class childThree {
public void SystemThree(){System.out.println("第三个子系统");}
}
// 实现外观类
public class facade {
private childOne one = new childOne();
private childTwo two = new childTwo();
private childThree three = new childThree();
// 提供外界的方法
public void use(int nums){
switch (nums){
case 1:
one.SystemOne();
break;
case 2:
two.SystemTwo();
break;
case 3:
three.SystemThree();
break;
default:
System.out.println("sorry!");
}

}
}
// 测试
public static void main(String[] args) {
facade facade = new facade();
facade.use(1); // 第一个子系统
facade.use(2); // 第二个子系统
}

使用外观模式的时机

  1. 在设计初期阶段,应该要有意识地将不同的两个层分离。比如经典的三层架构,就需要考虑在数据访问层和业务逻辑层、业务逻辑层和表示层的层与层之间建立外观Facade,这样可以为复杂的子系统提供一个简单的接口,使得耦合大大降低。
  2. 在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂。大多数的模式使用时也都会产生很多很小的类,这本是好事,但也给外部调用它们的用户程序带来了使用上的困难,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖。
  3. 在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须要依赖于它。此时用外观模式Facade也是非常合适的。

外观模式的优缺点

外观模式的优点包括:

  1. 简化了客户端代码:外观模式提供了一个统一的接口,客户端只需要调用外观类提供的方法即可完成复杂的操作,避免了直接调用多个子系统类的情况,从而简化了客户端代码。
  2. 降低了耦合度:由于外观模式将客户端与子系统解耦,使得子系统的变化对客户端的影响降到最低,从而提高了系统的可维护性、可扩展性和灵活性。
  3. 隐藏了复杂性:外观模式将复杂的子系统封装在外观类中,客户端不需要了解子系统的实现细节,从而降低了系统的复杂度,让客户端能够更加轻松地使用该系统。

外观模式的缺点包括:

  1. 不能很好地限制客户端使用子系统类:由于外观模式只是封装了一部分子系统的功能,而并未限制客户端直接使用子系统类,所以客户端可能会绕过外观类直接使用子系统类,从而导致系统的混乱和不稳定。
  2. 增加了系统的复杂度:外观模式需要增加一个外观类来封装子系统,这样就会增加系统的复杂度和代码量。

桥接模式

行为型

迭代器模式

模板方法模式

模板设计模式:定义算法的骨架

模板方法模式是一种行为型设计模式,定义了一个算法的框架,将其中一些步骤延迟到子类中实现。它使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤的具体实现方式。

模板方法模式通常由两部分组成:

  1. 抽象模板类(Abstract Template Class):定义了算法的框架和每个步骤应该如何执行,但并不实现全部方法,并且该类中的某些方法可以有默认实现。
  2. 具体实现类(Concrete Implementation Class):实现抽象模板类中的未实现方法,以及定义算法中的一些细节。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@FunctionalInterface
public interface InterfaceTemplate {
/**
* 定义顶层策略
*/
void strategy();
}
// 提供一个具体策略
public class InstanceTemplate implements InterfaceTemplate {
@Override
public void strategy() {
System.out.println("具体策略");
}
}
// 提供一个模板策略
public abstract class UserTemplate {
abstract void strategy(InterfaceTemplate template);
}
public class DefaultTemplate extends UserTemplate{

protected void strategy(InterfaceTemplate template) {
template.strategy();
}
}
// 用户可以自定义策略
// test
public static void main(String[] args) {
InstanceTemplate instanceTemplate = new InstanceTemplate();
instanceTemplate.strategy();

DefaultTemplate defaultTemplate = new DefaultTemplate();
defaultTemplate.strategy(() -> {
System.out.println("diy strategy");
});
}

模板方法模式是通过把不变行为搬移到超类,去除子类中的重复代码来体现它的优势。

由于策略模式不可能定义所有的策略,所有一般会搭配模板模式提供自定义策略

模板方法模式优点:

    1. 避免重复代码:将公共的方法提取到抽象模板类中,子类不再需要编写相同的代码段。
    2. 提高代码可扩展性:子类可以通过实现抽象模板类中的具体方法来改变算法的实现方式,从而达到扩展算法的目的,而不会影响到算法的整体结构。
    3. 降低代码耦合度:算法的框架和具体实现分别由抽象模板类和其子类实现,它们之间通过接口或者抽象父类进行交互,不直接依赖于具体的实现类。

缺点:

    1. 违反了单一职责原则:抽象模板类将算法的各个步骤定义在一个类中,其中包含了不同的逻辑分支,在一些情况下可能会使得该类变得比较庞大,难以维护和拓展。
    2. 可能导致代码复杂性增加:模板方法模式要求实现类必须提供某些具体的实现方法,这可能会导致实现类在实现这些方法时需要考虑更多的细节问题,从而增加了代码的复杂度。
    3. 破坏了封装性:实现类需要实现抽象模板类中定义的某些方法,这意味着实现类需要访问抽象模板类中的一些属性和方法,从而破坏了抽象模板类的封装性。

策略模式

策略模式(Strategy Pattern)是一种面向对象设计模式,它在一个对象中封装了不同的算法,使得这些算法可以相互替换。通过使用策略模式,客户端可以选择不同的算法来完成特定的任务,同时还可以轻松地替换算法和添加新的算法。

在策略模式中,一般有三个角色:策略接口、具体策略类和环境类。

  1. 策略接口定义了所有具体策略类所需要实现的方法
  2. 具体策略类实现了策略接口,并提供了不同的算法实现
  3. 环境类则持有一个策略接口类型的引用,并将实际的算法执行委托给该引用所指向的具体策略类。

策略模式的优点

  1. 可以方便地扩展和修改算法的实现,而不必修改环境类的代码。
  2. 可以将算法的实现与其他部分的代码分离,提高代码的可维护性和可复用性。

image-20230410224824707

实现一个商场记账

  1. 最开始的实现没有用到设计模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void main(String[] args) {
//商品单价
double price = 0.0;
//商品购买数量
int num = 0;
//当前商品合计费用
double totalPrices = 0.0;
//总计所有商品费用
double total = 0d;
Scanner sc = new Scanner(System.in);
// 不断输入进货的商品数量和单价,直到输入的价格或和数量小于0
while (true){
System.out.print("请输入商品单价:");
price = Double.parseDouble(sc.nextLine());
System.out.print("请输入商品数量:");
num = Integer.parseInt(sc.nextLine());
if (price <= 0 || num <= 0) {
break;
}
totalPrices = price * num;
total = total + totalPrices;
System.out.println("单价:" + price + "元 数量:" + num + " 合计:" + totalPrices + "元");
}
// 算出最终的 价格
System.out.println("总计:" + total + "元");
}

扩展性太弱了,商场出活动(打折等)代码就不能用了。

  1. 提前给出几个策略,并给出一个可扩展的策略

1
2
3
4
5
  根据需求选择合适的策略            
//AbstractStrategy strategy1 = new FullReduction(100,20);
//AbstractStrategy strategy2 = new Discounts(0.6);
//totalPrices = strategy1.strategy(price,num);
//totalPrices = strategy2.strategy(price,num);
  • 优点
  1. 简化单元测试:通过对应的接口进行单独测试,就不用测试到其他的功能接口。、
  2. 算法可重用性强:将算法封装成独立的类,使其可以在不同的应用中被复用,提高了代码的可维护性和可扩展性。
  3. 策略切换方便:对于相同的行为,在不同的场景下可能需要不同的实现算法,策略模式可以方便地切换算法实现。
  4. 避免使用多重条件分支语句:使用策略模式可以避免使用复杂的if-else分支语句,提高代码的可读性和可维护性,降低代码的圈复杂度
  5. 扩展性良好:向系统中增加新的策略类很容易,不需要修改原有代码,符合开闭原则。

命令模式

状态模式

责任链模式

备忘录模式

观察者模式

访问者模式

中介者模式

解释器模式