常见设计模式
2021/12/20 23:24:10
本文主要是介绍常见设计模式,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
文章目录
- 1、设计模式六大设计原则
- 1.1、单一职责原则
- 1.2、里氏替换原则
- 1.3、依赖倒置原则
- 1.4、接口隔离原则
- 1.5、迪米特法则
- 1.6、开闭原则
- 2、单例模式
- 2.1、饿汉式
- 2.2、懒汉式
- 2.3、枚举
- 2.4、优点
- 2.5、缺点
- 2.6、应用
- 3、工厂模式
- 3.1、简单工厂模式
- 3.1.1、优点
- 3.1.2、缺点
- 3.2、工厂方法模式
- 3.2.1、优点
- 3.2.2、缺点
- 3.3、抽象工厂模式
- 3.3.1、优点
- 3.3.2、缺点
- 3.4、工厂方法模式和抽象工厂模式的区别
- 4、代理模式
- 4.1、静态代理
- 4.2、动态代理
- 4.2.1、总结
- 5、装饰者模式
- 5.1、优点
- 5.2、缺点
- 5.3、静态代理和装饰者的区别
- 5.3.1、相同点
- 5.3.2、不同点
- 6、策略模式
- 6.1、优点
- 6.2、缺点
- 7、观察者模式
学习地址: 黑马设计模式
1、设计模式六大设计原则
1.1、单一职责原则
- 类的职责应该单一,一个方法只做一件事。尽量做到只有一个原因引起变化。
1.2、里氏替换原则
- 所有引用基类的地方,必须能够使用其子类直接替换。(与面向对象的继承特性密切相关)。
1.3、依赖倒置原则
- 面向接口编程,这是面向对象设计的精髓之一,这样可以减少类与类之间的耦合性,提高系统的稳定性,降低并行开发引起的风险。
- 具体表现:
- 模块之间依赖通过接口发生,实现类之间不直接依赖。
- 接口或者抽象类不依赖于具体实现类。
- 实现类依赖于接口或者实现类。
1.4、接口隔离原则
- 客户端不应该依赖于它不需要的接口(此时接口指类和接口)。
- 建立单一的接口,不要建立臃肿庞大的接口。
1.5、迪米特法则
- 一个对象应该对其他对象有最少的了解。对类的低耦合提出了明确的要求。
- 如果两个软件实体无须直接通信,那么就不应当直接的相互调用,可以通过第三方转发该调用。目的就是降低类之间的耦合度,提高模块的相对独立性。
- 朋友:当前对象本身,当前对象的成员对象,当前对象所创建的对象,当前对象的方法参数等。
- 举例:老师请班长点名,此时老师之和班长产生耦合,不应该和学生有耦合。
1.6、开闭原则
- 对扩展开放,对修改关闭。目的:使程序的扩展性好,抑郁维护和升级。
2、单例模式
- 该类负责创建自己的对象,同时确保只有单个对象被创建。
2.1、饿汉式
- 经典饿汉式
public class Singleton { //1,私有构造方法 private Singleton() {} //2,在本类中创建本类对象 private static Singleton instance = new Singleton(); //3,提供一个公共的访问方式,让外界获取该对象 public static Singleton getInstance() { return instance; } }
- 静态代码块
public class Singleton { //私有构造方法 private Singleton() {} //声明Singleton类型的变量 private static Singleton instance; //null //在静态代码块中进行赋值 static { instance = new Singleton(); } //对外提供获取该类对象的方法 public static Singleton getInstance() { return instance; } }
2.2、懒汉式
- 同步方法
public class Singleton { //私有构造方法 private Singleton() {} //声明Singleton类型的变量instance private static Singleton instance; //只是声明一个该类型的变量,并没有进行赋值 //对外提供访问方式 public static synchronized Singleton getInstance() { //判断instance是否为null,如果为null,说明还没有创建Singleton类的对象 //如果没有,创建一个并返回,如果有,直接返回 if(instance == null) { //线程1等待,线程2获取到cpu的执行权,也会进入到该判断里面 instance = new Singleton(); } return instance; } }
- 使用volatile的DCL
public class Singleton { //私有构造方法 private Singleton() {} //声明Singleton类型的变量 private static volatile Singleton instance; //对外提供公共的访问方式 public static Singleton getInstance() { //第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象 if(instance == null) { synchronized (Singleton.class) {//因为是静态的,所以当前锁为CLass对象 //第二次判断 if(instance == null) { instance = new Singleton(); } } } return instance; } }
2.3、枚举
public enum Singleton { INSTANCE; }
public class Client { public static void main(String[] args) { Singleton instance = Singleton.INSTANCE; Singleton instance1 = Singleton.INSTANCE; System.out.println(instance == instance1); } }
2.4、优点
- 确保所有的对象都访问一个实例。
- 具有一定的伸缩性。
- 避免对共享资源的多种占用。
2.5、缺点
- 单例模式没有抽象层,因此扩展有很大困难。
- 单例模式一定程度违背单一职责原则。
- 不适用于变化的对象。
2.6、应用
- 数据库连接池
- 线程池
3、工厂模式
- 出现的原因:违背了开闭原则。如果创建的时候直接new对象,就会对该对象耦合严重,如果要更换对象,所有new对象的地方都需要修改一遍。
3.1、简单工厂模式
- 不是一种设计模式,更像一种编程习惯。
- 方法:将原来new对象的方法单独的抽象为一个类,这样,在真正需要创建对象的时候,直接调用方法即可,降低了耦合度。
- 结构:
- 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品 :实现或者继承抽象产品的子类。
- 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
- 类图:
3.1.1、优点
- 降低了代码修改的可能性,更加容易扩展。
3.1.2、缺点
- 违背开闭原则
3.2、工厂方法模式
- 定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。
- 是多态的一个很好的应用。
- 结构:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
- 类图:
- 抽象工厂
public interface CoffeeFactory { //创建咖啡对象的方法 Coffee createCoffee(); }
- 具体的工厂创建具体的对象
public class AmericanCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new AmericanCoffee(); } }
public class LatteCoffeeFactory implements CoffeeFactory { public Coffee createCoffee() { return new LatteCoffee(); } }
- 抽象类
public abstract class Coffee { public abstract String getName(); //加糖 public void addsugar() { System.out.println("加糖"); } //加奶 public void addMilk() { System.out.println("加奶"); } }
- 具体对象(通过继承)
public class AmericanCoffee extends Coffee { public String getName() { return "美式咖啡"; } }
public class LatteCoffee extends Coffee { public String getName() { return "拿铁咖啡"; } }
- 咖啡店生产咖啡
public class CoffeeStore { //私有工厂对象 private CoffeeFactory factory; //根据具体的工厂获取对象 public void setFactory(CoffeeFactory factory) { this.factory = factory; } //点咖啡功能 public Coffee orderCoffee() { Coffee coffee = factory.createCoffee(); //加配料 coffee.addMilk(); coffee.addsugar(); return coffee; } }
- 测试
public class Client { public static void main(String[] args) { //创建咖啡店对象 CoffeeStore store = new CoffeeStore(); //创建对象 //CoffeeFactory factory = new AmericanCoffeeFactory(); CoffeeFactory factory = new LatteCoffeeFactory(); store.setFactory(factory); //点咖啡 Coffee coffee = store.orderCoffee(); System.out.println(coffee.getName()); } }
3.2.1、优点
- 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
3.2.2、缺点
- 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
3.3、抽象工厂模式
- 工厂方法模式只考虑生产同类产品,但现实许多工厂是综合型的工厂。
- 是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
- 抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
- 结构:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
- 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
- 现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。那么此时符合抽象工厂模式,类图:
- 同一级别的产品(咖啡和甜点)
public abstract class Coffee { public abstract String getName(); //加糖 public void addsugar() { System.out.println("加糖"); } //加奶 public void addMilk() { System.out.println("加奶"); } }
public abstract class Dessert { public abstract void show(); }
- 具体的对象
public class AmericanCoffee extends Coffee { public String getName() { return "美式咖啡"; } }
public class LatteCoffee extends Coffee { public String getName() { return "拿铁咖啡"; } }
public class MatchaMousse extends Dessert { public void show() { System.out.println("抹茶慕斯"); } }
public class Trimisu extends Dessert { public void show() { System.out.println("提拉米苏"); } }
- 同一产品族(意大利风味、美式风味)
public class ItalyDessertFactory implements DessertFactory { public Coffee createCoffee() { return new LatteCoffee(); } public Dessert createDessert() { return new Trimisu(); } }
public class AmericanDessertFactory implements DessertFactory { public Coffee createCoffee() { return new AmericanCoffee(); } public Dessert createDessert() { return new MatchaMousse(); } }
- 测试类
public class Client { public static void main(String[] args) { //创建的是意大利风味甜品工厂对象 ItalyDessertFactory factory = new ItalyDessertFactory(); // AmericanDessertFactory factory = new AmericanDessertFactory(); //获取拿铁咖啡和提拉米苏甜品 Coffee coffee = factory.createCoffee(); Dessert dessert = factory.createDessert(); System.out.println(coffee.getName()); dessert.show(); } }
3.3.1、优点
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
3.3.2、缺点
- 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
3.4、工厂方法模式和抽象工厂模式的区别
- 抽象工厂关键在于产品之间的抽象关系,至少需要两个产品;工厂方法在于生成产品,不关注产品之间的关系,可以只生成一个产品。
4、代理模式
- 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
- Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
- 结构:
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
4.1、静态代理
【例】火车站卖票
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下:
使用组合,在代理类中,将真正的类作为自己的属性。方法还是调用的真正的类去执行的。测试的时候直接访问的是代理对象,即:代理类作为访问对象和目标对象的中介,同时对方法进行了增强。
//卖票接口 public interface SellTickets { void sell(); } //火车站 火车站具有卖票功能,所以需要实现SellTickets接口 public class TrainStation implements SellTickets { public void sell() { System.out.println("火车站卖票"); } } //代售点 public class ProxyPoint implements SellTickets { private TrainStation station = new TrainStation(); public void sell() { System.out.println("代理点收取一些服务费用"); station.sell(); } } //测试类 public class Client { public static void main(String[] args) { ProxyPoint pp = new ProxyPoint(); pp.sell(); } }
4.2、动态代理
- 动态代理是在实现阶段不关心代理类,而在运行阶段才指定哪一个对象。
- 接口:
public interface UserDao { int add(int a,int b); }
- 接口实现类:(需要被增强的类)
public class UserDaoImpl implements UserDao { @Override public int add(int a, int b) { System.out.println("add方法执行了....."); return a+b; } }
- 创建代理对象
//创建代理对象代码 class UserDaoProxy implements InvocationHandler { //1 把创建的是谁的代理对象,把谁传递过来 //有参数构造传递 private Object obj; public UserDaoProxy(Object obj) { this.obj = obj; } //增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args)); //被增强的方法执行 Object res = method.invoke(obj, args); //方法之后 System.out.println("方法之后执行...."+obj); return res; } }
- 测试
public class JDKProxy { public static void main(String[] args) { //创建接口实现类代理对象 Class[] interfaces = {UserDao.class}; UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao)Proxy.newProxyInstance(UserDao.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1, 2); System.out.println("result:"+result); } }
4.2.1、总结
- 首先需要一个接口、以及该接口的实现类(需要被增强的类)。
- 随后创建代理对象(写一个类),代理对象要实现InvocationHandler接口,要实现invoke方法(增强的具体逻辑),在,在类中,把创建是哪个接口的代理对象,就把那个接口对象传过来,使用注入的方式进行设置。
- 最后在测试的时候,一定是调用
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
,第一个参数值针对哪个接口的实现类(也可以写接口)加载器;第二个参数是代理类要实现的接口列表(记:接口.class就是一个接口列表);第三个参数代理对象。
5、装饰者模式
五分钟学设计模式-装饰器模式
- 指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
- 结构:
- 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
类图:
3. 举例:有一个产品,但是功能很少,厂家说出第二代,那么这个第二代就要继承第一代的功能,此时你自己动手给这个第一个代加了一个壳子,使用这个壳子完成了想要的功能,这个壳子就是装饰器,同时保持了第一代产品功能的不改变。
4. 在JDK源码中,IO流大量的使用了装饰器模式。
5. 代码示例:
- 接口:
public interface Robot { void doSomething(); }
- 初代机器人
public class FirstRobot implements Robot{ public void doSomething() { System.out.println("唱歌"); System.out.println("移动"); } }
- 二代机器人
public class RobotDecorator implements Robot{ private Robot robot; public RobotDecorator(Robot robot) { this.robot = robot; } public void doSomething() { robot.doSomething(); } public void doMoreThing(){ robot.doSomething(); System.out.println("跳舞"); } }
- 测试
public class DecoratorPattern { public static void main(String[] args) { RobotDecorator robotDecorator = new RobotDecorator(new FirstRobot()); robotDecorator.doMoreThing(); } }
5.1、优点
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
5.2、缺点
- 需要编写更多的代码 , 生成更多的类 , 程序的复杂性增加了 。
5.3、静态代理和装饰者的区别
5.3.1、相同点
- 都要实现与目标类相同的业务接口
- 在两个类中都要声明目标对象
- 都可以在不修改目标类的前提下增强目标方法
5.3.2、不同点
- 目的不同:装饰者是为了增强目标对象;静态代理是为了保护和隐藏目标对象
- 获取目标对象构建的地方不同:装饰者是由外界传递进来,可以通过构造方法传递;静态代理是在代理类内部创建,以此来隐藏目标对象
6、策略模式
- 定义一系列算法,将每个算法都封装起来,并且使他们之间可以互相转换,并且算法的变化是不会影响使用算法的用户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理
- 策略模式让算法独立于使用它的客户而变化,也称为政策模式。
- 结构:
- 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
- 举个例子:我们在编写代码的时候使用的IDE,可以选择用idea、eclipse等,这些IDE就是一个具体的算法。
- 案例:【商场促销】
促销活动共同的接口(抽象策略类)
public interface Strategy { void show(); }
具体算法
public class StrategyA implements Strategy { public void show() { System.out.println("买一送一"); } }
public class StrategyB implements Strategy { public void show() { System.out.println("满200元减50元"); } }
public class StrategyC implements Strategy { public void show() { System.out.println("满1000元加一元换购任意200元以下商品"); } }
促销员(环境类):此时将策略对象设置为自己的一个属性
public class SalesMan { //聚合策略类对象 private Strategy strategy; public SalesMan(Strategy strategy) { this.strategy = strategy; } public Strategy getStrategy() { return strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; } //由促销员展示促销活动给用户 public void salesManShow() { strategy.show(); } }
测试类
public class Client { public static void main(String[] args) { //春节来了,使用春节促销活动 SalesMan salesMan = new SalesMan(new StrategyA()); //展示促销活动 salesMan.salesManShow(); System.out.println("=============="); //中秋节到了,使用中秋节的促销活动 salesMan.setStrategy(new StrategyB()); //展示促销活动 salesMan.salesManShow(); System.out.println("=============="); //圣诞节到了,使用圣诞节的促销活动 salesMan.setStrategy(new StrategyC()); //展示促销活动 salesMan.salesManShow(); } }
6.1、优点
- 策略类可以自由切换。(由于策略类都实现同一个接口,所以使它们之间可以自由切换。)
- 易于扩展。(增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“)
- 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
6.2、缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
7、观察者模式
- 定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变的时候,其相关依赖对象皆得到通知并被自动更新。
- 也叫发布-订阅模式。
- 示例1:【微信公众号】。
抽象主题角色类(公众号的接口)
public interface Subject { //添加订阅者(添加观察者对象) void attach(Observer observer); //删除订阅者 void detach(Observer observer); //通知订阅者更新消息 void notify(String message); }
抽象观察者类
public interface Observer { void update(String message); }
具体主题角色类(订阅的公众号)
public class SubscriptionSubject implements Subject { //定义一个集合,用来存储多个观察者对象 private List<Observer> weiXinUserList = new ArrayList<Observer>(); public void attach(Observer observer) { weiXinUserList.add(observer); } public void detach(Observer observer) { weiXinUserList.remove(observer); } public void notify(String message) { //遍历集合 for (Observer observer : weiXinUserList) { //调用观察者对象中的update方法 observer.update(message); } } }
具体的观察者角色类(每个用户)
public class WeiXinUser implements Observer { private String name; public WeiXinUser(String name) { this.name = name; } public void update(String message) { System.out.println(name + "-" + message); } }
测试类
public class Client { public static void main(String[] args) { //1,创建公众号对象 SubscriptionSubject subject = new SubscriptionSubject(); //2,订阅公众号 subject.attach(new WeiXinUser("孙悟空")); subject.attach(new WeiXinUser("猪悟能")); subject.attach(new WeiXinUser("沙悟净")); //3,公众号更新,发出消息给订阅者(观察者对象) subject.notify("传智黑马的专栏更新了!"); } }
示例2:【欠债还钱】
找人借钱的接口:
public interface Debit { void borrow(Credit credit);//借钱 void notifyCredits();//通知还钱 }
给人借钱的接口
public interface Credit { void takeMoney(); }
具体给人借钱的对象
public class Wangwu implements Credit{ public void takeMoney() { System.out.println("王五要钱"); } }
public class Zhaosi implements Credit{ public void takeMoney() { System.out.println("赵四要钱"); } }
具体找人借钱的对象
public class Zhangsan implements Debit{ private List<Credit> allCredits = new ArrayList<Credit>(); private Integer state = 0;//状态的改变位置,1表示有钱 public void borrow(Credit credit) { allCredits.add(credit); } public void notifyCredits(Integer state) { this.state = state; allCredits.forEach(credit -> credit.takeMoney()); } }
测试类
public class ObserverPattern { public static void main(String[] args) { Debit zhangsan = new Zhangsan(); zhangsan.borrow(new Wangwu()); zhangsan.borrow(new Zhaosi()); //状态改变 zhangsan.notifyCredits(8); } }
这篇关于常见设计模式的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-06小米11i印度快充版ROM合集:极致体验,超越期待
- 2024-10-06【ROM下载】小米11i 5G 印度版系统, 疾速跃迁,定义新速度
- 2024-10-06【ROM下载】小米 11 青春活力版,青春无极限,活力全开
- 2024-10-05小米13T Pro系统合集:性能与摄影的极致融合,值得你升级的系统ROM
- 2024-10-01基于Python+Vue开发的医院门诊预约挂号系统
- 2024-10-01基于Python+Vue开发的旅游景区管理系统
- 2024-10-01RestfulAPI入门指南:打造简单易懂的API接口
- 2024-10-01初学者指南:了解和使用Server Action
- 2024-10-01Server Component入门指南:搭建与配置详解
- 2024-10-01React 中使用 useRequest 实现数据请求