作为学习Spring的一些基础,我们对常用设计模式也需要有一些了解。
一、基础概念
设计模式:代表了最佳的实践,开发人员在开发过程中对一般问题的解决方案。这些解决方案经过长时间的试验和错误总结出来的。并且在我们使用的常用框架中,也都会使用设计模式来解决一些问题。例如:Spring 的BeanFactory 就是简单工厂模式,Spring AOP模块就是基于动态代理(代理模式)的,并且 BeforeAdvice、AfterAdvice、ThrowsAdvice三种通知类型的支持,是通过适配器模式实现的。
设计模式七大原则
开闭原则:对扩展开放,对修改关闭。
里氏替换原则:子类可以拓展父类的功能,但不能改变父类原有的功能(尽量不要重写父类的方法)。
依赖倒置原则:面向接口编程,不要面向实现编程。
单一职责原则:一个类只负责一项职责,如果职责太多,则进行拆分。
接口隔离原则:尽量将臃肿的接口拆分成更具体的接口,一个类对另一个类的依赖应该建立在最小的接口上。
迪米特法则:最少知道原则,一个对象应该对另一个对象有最少的了解。
合成复用原则:复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
23中设计模式中可划分成三类
创建型模式(5种)
单例模式(Singleton):某类只能生成一个类实例,并且提供一个全局访问点获取该实例。
工厂方法模式(Factory Method):定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。(只考虑同一类产品)
抽象工厂模式(Abstract Factory):定义一个创建对象工厂的接口,不需要显示指定它们的类,每个创建出来的工厂都能按照其工厂模式提供对象。(复合型工厂)
原型模式(Prototype):实现一个原型接口,该接口用于创建当前对象的克隆。
建造者模式(Builder):将一个复杂对象分解成多个相对简单的部分,根据不同需要分别创建他们,最后构建成复杂对象。
结构型模式(7种)
适配器模式(Adapter):将一个接口转换成客户希望的另一个接口,使得原本因为接口不兼容而不能一起工作的类能一起工作。
装饰器模式(Decorator):用来包装原有类,在保持类方法的完整性前提下,提供额外的功能。
代理模式(Proxy):为对象提供一个代理,以控制对该对象的访问,并可以对其限制、增强、修改该对象的一些他特性,客户端间接访问该对象。
外观模式(Facade):为子系统中的一组接口提供一个一致的接口,来隐藏系统的复杂性。
组合模式(Composite):又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次
享元模式(Flyweight):主要用于减少创建对象的数量,以减少内存占用和提高性能。
桥接模式(Bridge):将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
行为型模式(11种)
模板方法模式(Template Method):定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
策略模式(Stragegy):定义一些列算法,将其一个个封装起来,并且使他们可以互相替换,且算法的变化不会影响使用算法的客户。
观察者模式(Observer):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
访问者模式(Visitor):在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
中介者模式(Mediator):定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
责任链模式(Chain of Responsibility):为请求创建了一个接收者对象的链,把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
备忘录模式(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
命令模式(Command):将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
状态模式(State):允许一个对象在其内部状态发生改变时改变其行为能力。
解释器模式(Interpreter):提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
在对上述一些概念和模式有了一个大致了解后,其实在着手写几个常用设计模式,那对于设计模式这部分内容就有一个理解了。个人觉得不必要每种设计模式都要去做具体实现,日常工作中也不是每天都要我们去写各种设计模式。在对设计模式有一定了解后,其实在我们解决一般性问题时,可以考虑去应用那种设计模式作为解决方案。之后再去关注具体实现就好。可能,简单算法(方法)要比引入设计模式更加容易,但用于设计模式组织起来的代码,拓展性、可读性、可靠性、可维护性会更高。
二、进入正题
常用的几种设计模式
1. 单例模式
//饿汉模式 只有一个静态的对象实例,不可被改变,线程安全的
class HungrySingleton{
//类加载时创建实例
private static final HungrySingleton hungrySingleton = new HungrySingleton();
//构造方法私有化,避免外部实例化
private HungrySingleton(){
}
//提供getInstance方法以获取实例
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
//懒汉模式 去掉 volatile 和 synchronized 线程不安全,但需要同步,效率低
class LazySingleton {
// volatile 保证属性的可见性和有序性
private static volatile LazySingleton lazySingleton = null;
//构造方法私有化,避免外部实例化
private LazySingleton(){
}
//提供一个getInstance方法以获取对象实例 synchronized 保证线程安全性
public static synchronized LazySingleton getInstance(){
//如果singletonPatterns对象为空,new 一个对象
if(lazySingleton==null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
public class SingletonPatterns {
public static void main(String[] args) {
HungrySingleton.getInstance();
LazySingleton.getInstance();
}
}
2.简单工厂模式
添加新用户时,需要修改工厂类,违背了开闭原则。
package patterns;
/** 用户接口 */
interface IUser{
void showName();
}
/** A 用户实现 */
class AUserImpl implements IUser{
@Override
public void showName() {
System.out.println("A UserImpl");
}
}
/** B 用户实现 */
class BUserImpl implements IUser{
@Override
public void showName() {
System.out.println("B UserImpl");
}
}
/** 用户创建工厂 */
class UserFactory{
//通过userId判断返回哪个用户
public static IUser getUser(int userId){
if(userId==1){
return new AUserImpl();
}else if(userId==2){
return new BUserImpl();
}else {
return null;
}
}
}
public class SimpleFactoryPattern {
public static void main(String[] args) {
UserFactory.getUser(1).showName();
UserFactory.getUser(2).showName();
}
}
3.工厂方法模式
优点:用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;
缺点:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
package patterns;
/** 抽象工厂接口 */
interface IFactory{
IProduct newProduct();
}
/** 产品接口 */
interface IProduct{
void getProductName();
}
/** A产品工厂 */
class AProductFactory implements IFactory{
@Override
public IProduct newProduct() {
return new AProductImpl();
}
}
/** A产品实现 */
class AProductImpl implements IProduct{
@Override
public void getProductName() {
System.out.println("我是 AProduct");
}
}
/** B产品工厂 */
class BProductFactory implements IFactory{
@Override
public IProduct newProduct() {
return new BProductImpl();
}
}
/** B产品实现 */
class BProductImpl implements IProduct{
@Override
public void getProductName() {
System.out.println("我是 BProduct");
}
}
public class FactoryMethodPattern {
public static void main(String[] args) {
IFactory iFactory = new AProductFactory();
iFactory.newProduct().getProductName();
iFactory = new BProductFactory();
iFactory.newProduct().getProductName();
}
}
4.抽象工厂模式
优点:可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
当增加一个新的产品族时不需要修改原代码,满足开闭原则。
缺点:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
package patterns;
/** 电话(品类)接口 */
interface IPhone{
void call(); //打电话
void playGane(); //打游戏
}
/** 电视(品类)接口 */
interface ITV{
void watchTV(); //看电视
}
/** 小米、华为 生产手机 (具体产品)*/
class XiaomiPhone implements IPhone{
@Override
public void call() {
System.out.println("使用小米手机打电话");
}
@Override
public void playGane() {
System.out.println("使用小米手机玩游戏");
}
}
class HuaweiPhone implements IPhone{
@Override
public void call() {
System.out.println("使用华为手机打电话");
}
@Override
public void playGane() {
System.out.println("使用华为手机玩游戏");
}
}
/** 小米、海尔 生产电视(具体产品) */
class XiaomiTV implements ITV{
@Override
public void watchTV() {
System.out.println("用小米电视看电影");
}
}
class HaierTV implements ITV{
@Override
public void watchTV() {
System.out.println("用海尔电视看电影");
}
}
/** 抽象工厂(创建不同等级的产品) */
interface IAbstractFactory{
IPhone getIPhone();
ITV getITV();
}
/** 具体工厂(抽象工厂具体实现),TB、JD */
class TBShop implements IAbstractFactory{
@Override
public IPhone getIPhone() {
return new XiaomiPhone(); //TB卖小米手机
}
@Override
public ITV getITV() {
return new HaierTV(); //TB卖海尔电视
}
}
class JDSHop implements IAbstractFactory{
@Override
public IPhone getIPhone() {
return new HuaweiPhone(); //JD卖华为手机
}
@Override
public ITV getITV() {
return new XiaomiTV(); //JD卖小米电视
}
}
public class AbstractFactoryPattern {
public static void main(String[] args) {
//TB上买一个手机玩游戏,打电话
IAbstractFactory iAbstractFactory = new TBShop();
iAbstractFactory.getIPhone().call();
//JD上买一个电视
iAbstractFactory = new JDSHop();
iAbstractFactory.getITV().watchTV();
}
}
5.适配器模式
优点:1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点:过多使用会导致系统比较乱,所以尽量保证适配器的类型不要太多。
目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
package patterns;
/** 已存在组件(适配者)*/
class Adaptee{
void printInfo(){
System.out.println("我是适配者类");
}
}
/** 目标接口 */
interface ITarget{
void showInfo(); //实现类,都需要显示一段信息
}
/** 适配器,将适配者类以目标接口形式使用 */
class ClassApapter extends Adaptee implements ITarget{
@Override
public void showInfo() {
super.printInfo();
}
}
public class AdapterPattern {
public static void main(String[] args) {
ITarget target = new ClassApapter(); //通过适配器创建
target.showInfo();
}
}
6.装饰器模式
优点:在不改变被装饰类的情况下,可通过多个不同的具体装饰类,对被装饰器类进行装饰。
缺点:多层装饰比较复杂。
抽象构件(Component):定义一个抽象接口以规范准备接收附加责任的对象。
具体构件(Concrete Component):实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰(Decorator):继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰(Concrete Decorator):实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
package patterns;
/** 手机(抽象构件) */
interface Phone{
void color(); //手机颜色
}
/** 锤子手机(具体构件) */
class ChuiziPhone implements Phone{
@Override
public void color() {
System.out.println("一部红色的手机");
}
}
/** 手机壳(抽象装饰 )*/
abstract class AbstractDecorator implements Phone{
private Phone phone; //构造函数传入抽象构件
AbstractDecorator(Phone phone){
this.phone = phone;
}
@Override
public void color() {
this.phone.color();
phoneShell();
}
//新加一个手机壳
abstract void phoneShell();
}
/** 彩色手机壳(具体装饰) */
class ColourShell extends AbstractDecorator{
ColourShell(Phone phone) {
super(phone);
}
@Override
void phoneShell() {
System.out.println("加了一个彩色的手机壳");
}
}
/** 黑色手机壳(具体装饰) */
class BlackShell extends AbstractDecorator{
BlackShell(Phone phone) {
super(phone);
}
@Override
void phoneShell() {
System.out.println("加了一个黑色的手机壳");
}
}
public class DecoratorPattern {
public static void main(String[] args) {
Phone phone = new ChuiziPhone();
AbstractDecorator abstractDecorator = new ColourShell(phone);
abstractDecorator.color();
abstractDecorator = new BlackShell(phone);
abstractDecorator.color();
}
}
7.策略模式
优点:1.算法可以自由切换。2.避免使用多重条件判断。 3.扩展性良好。
缺点:1.策略类会增多。 2.所有策略类都需要对外暴露。
抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
package patterns;
/** 支付方式(抽象策略)*/
interface PayStrategy{
void payment();
}
/** 支付宝(具体策略)*/
class AliPayStrategy implements PayStrategy{
@Override
public void payment() {
System.out.println("使用支付宝支付");
}
}
/** 微信(具体策略)*/
class WechatStrategy implements PayStrategy{
@Override
public void payment() {
System.out.println("使用微信支付");
}
}
/** 去支付(环境类)*/
class ToPayment{
private PayStrategy payStrategy;
public ToPayment(PayStrategy payStrategy){
this.payStrategy = payStrategy;
}
public PayStrategy getPayStrategy(){
return this.payStrategy;
}
}
public class StrategyPattern {
public static void main(String[] args) {
ToPayment toPayment = new ToPayment(new AliPayStrategy());
toPayment.getPayStrategy().payment();
toPayment = new ToPayment(new WechatStrategy());
toPayment.getPayStrategy().payment();
}
}
8.模版方法模式
优点:1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
(1) 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。
① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法(父类实现)。
② 基本方法:是整个算法中的一个步骤,包含以下几种类型。
抽象方法:在抽象类中申明,由具体子类实现。
具体方法:在抽象类中已经实现,包括用于判断的逻辑方法或需要子类重写的空方法两种。
(2) 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和模版方法,它们是一个顶级逻辑的一个组成步骤。
package MyCode.patterns;
/** 抽象类 (模版抽象) */
abstract class Games{
//抽象方法
abstract void init();
abstract void start();
abstract void end();
//模版方法
void playGame(){
init();
start();
end();
}
}
/** 具体子类(具体实现)*/
class LOL extends Games{
@Override
void init() {
System.out.println("【LOL】打开游戏");
}
@Override
void start() {
System.out.println("【LOL】开始游戏");
}
@Override
void end() {
System.out.println("【LOL】游戏结束");
}
}
class CF extends Games{
@Override
void init() {
System.out.println("【CF】打开游戏");
}
@Override
void start() {
System.out.println("【CF】开始游戏");
}
@Override
void end() {
System.out.println("【CF】游戏结束");
}
}
public class TemplateMethodPattern {
public static void main(String[] args) {
Games games = new LOL();
games.playGame();
System.out.println("换个游戏。。。。");
games = new CF();
games.playGame();
}
}
9.观察者模式
优点:1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点:1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
package patterns;
import java.util.ArrayList;
import java.util.List;
/** 订单(抽象目标) */
abstract class AbstractOrderSubject{
//观察者集合
List<IOBServer> goodsOBServerList = new ArrayList<IOBServer>();
//添加观察者
void setOrderOBServer(IOBServer goodsOBServer){
goodsOBServerList.add(goodsOBServer);
}
void removeOrderOBServer(IOBServer goodsOBServer){
goodsOBServerList.remove(goodsOBServer);
}
//通知观察者方法
abstract void notifyOBServer();
}
/** 订单具体目标 */
class OrderOBServer extends AbstractOrderSubject{
@Override
void notifyOBServer() {
System.out.println("有人下单了,准备进行通知");
for(IOBServer iobServer : goodsOBServerList){
iobServer.obOrderState();
}
}
}
/** 抽象观察者 */
interface IOBServer{
void obOrderState(); //观察订单状态
}
/** 餐馆(具体观察者)*/
class HotelOBServer implements IOBServer{
@Override
public void obOrderState() {
System.out.println("HotelOBServer,收到通知,我们开始做菜");
}
}
/** 美团(具体观察者)*/
class MeituanOBServer implements IOBServer{
@Override
public void obOrderState() {
System.out.println("MeituanOBServer,收到通知,我们安排骑手取餐");
}
}
public class OBServerPattern {
public static void main(String[] args) {
AbstractOrderSubject orderSubject = new OrderOBServer();
HotelOBServer hotelOBServer = new HotelOBServer();
MeituanOBServer meituanOBServer = new MeituanOBServer();
orderSubject.setOrderOBServer(hotelOBServer);
orderSubject.setOrderOBServer(meituanOBServer);
orderSubject.notifyOBServer();
}
}
网友评论