常用到的设计模式

工厂模式

简单工厂模式

简单工厂一般是用一个工厂创建多个类的实例。
工厂模式一般是指一个工厂服务一个接口,为这个接口的实现类进行实例化

抽象工厂模式是指一个工厂服务于一个产品族,一个产品族可能包含多个接口,接口又会包含多个实现类,通过一个工厂就可以把这些绑定在一起,非常方便。
简单工厂模式属于类的创建型模式,又叫静态工厂方法模式. 就是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类.

1.工厂(Creator)角色简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
2.抽象(Product)角色简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
3.具体产品(Concrete Product)角色简单工厂模式所创建的具体实例对象

简单工厂的UML图

sports:抽象角色Basketball和Football:具体产品角色sportsFactory:工厂角色

代码实例:
1.创建抽象类sports,同时有一个运动方法

public interface Sports{
/**
*  运动
*/
public void play();
}

2.定义了一个足球类和篮球类,同样有运动方法


public class Basketball implements Sports{  
/* 
* 运动     
*/  
public void play(){        System.out.println("打篮球...");   
}
}
public class Football implements Sports{   
/*     
* 运动     
*/    public void play(){        System.out.println("踢足球啦...");
}
}

3.写一个工厂类,用来创造足球运动和篮球运动。

  1. 单独创建实例方法
public class SportsFactory{
/*
*获取BasketBall类的实例
*/
public static Sports getBasketBall(){
return new BasketBall();
}

/*
*获取FootBall类实例
*/
public static Sports getFootBall(){
return new FootBall();
}
}

这种方法,直接用两个不同的方法直接创建两个实例对象。

2.逻辑判断的方式


public class SportsFactory {    
/*
* getSports方法,获得所有产品对象
 */    
 public static Sports getSports(String type) throws InstantiationException, IllegalAccessException, ClassNotFoundException {        
 if(type.equalsIgnoreCase("Basketball")) {            
 return Basketball.class.newInstance();        
 } else if(type.equalsIgnoreCase("Football")) {
 return Football.class.newInstance();        
 } else {           
 System.out.println("找不到相应的实例化=类");            
 return null;        
 }    
 }}

先传入一个类型参数,然后判断,再用反射的方式返回对象。前面这两种方式都不是最优的方式,因为他们都违背了开闭原则
开闭原则定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开放-封闭原则的意思就是说,你设计的时候,时刻要考虑,尽量让这个类是足够好,写好了就不要去修改了,如果新需求来,我们增加一些类就完事了,原来的代码能不动则不动。这个原则有两个特性,一个是说“对于扩展是开放的”,另一个是说“对于更改是封闭的”。面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。这就是“开放-封闭原则”的精神所在。
3.反射实现方法


public class SportsFactory {    
/*
 * getSports方法,获得所有产品对象
 */   
 public static Sports getSports(String type) throws InstantiationException, IllegalAccessException, ClassNotFoundException {        
 Class Sports = Class.forName(type);        
 return (Sports) Sports.newInstance();    
 }}

这种方法,通过反射的方式实现,不管我们还需要扩展什么其他功能都不用再更改这个工厂类的代码了,所以就符合开闭原则了。
简单工厂模式应用场景

优点:工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化。缺点:由于工厂类集中了所有实例的创建逻辑,违反了开闭原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;

总结:

简单工厂模式的实质是由一个工厂类个南山传入的参数,动态决定应该创建哪一个产品类.
一个抽象的接口,多个抽象接口的实现类,一个工厂类,用来实例化抽象的接口

Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。

工厂方法模式:有四个角色,抽象工厂模式,具体工厂模式,抽象产品模式,具体产品模式。不再是由一个工厂类去实例化具体的产品,而是由抽象工厂的子类去实例化产品

// 抽象产品角色
public interface Moveable {   
void run();
}
// 具体产品角色
public class Plane implements Moveable {   
@Override   public void run() {       
System.out.println("plane....");   
    }
}
public class Broom implements Moveable {   
@Override   public void run() {       
System.out.println("broom.....");   
    }
}
// 抽象工厂
public abstract class VehicleFactory {   
abstract Moveable create();
}
// 具体工厂
public class PlaneFactory extends VehicleFactory {   
public Moveable create() {       
return new Plane();   
    }
 }
public class BroomFactory extends VehicleFactory {  
public Moveable create() {       
return new Broom();   
    }
}
// 测试类
public class Test {   
public static void main(String[] args) {       
VehicleFactory factory = new BroomFactory();       
Moveable m = factory.create();       
m.run();   
    }
}

单例模式

单例模式保证全局的单例类只有一个实例,这样的话使用的时候直接获取即可,比如数据库的一个连接,Spring里的bean,都可以是单例的。
单例模式一般有5种写法。
第一种是饿汉模式,先把单例进行实例化,获取的时候通过静态方法直接获取即可。缺点是类加载后就完成了类的实例化,浪费部分空间。
第二种是饱汉模式,先把单例置为null,然后通过静态方法获取单例时再进行实化,但是可能有多线程同时进行实例化,会出现并发问题。
第三种是逐步改进的方法,一开始可以用synchronized关键字进行同步,但是开销太大,而后改成使用volatile修饰单例,然后通过一次检查判断单例是否已初始化,如果未初始化就使用synchronized代码块,再次检查单例防止在这期间被初始化,而后才真正进行初始化。
第四种是使用静态内部类来实现,静态内部类只在被使用的时候才进行初始化,所以在内部类中进行单例的实例化,只有用到的时候才会运行实例化代码。然后外部类再通过静态方法返回静态内部类的单例即可。
第五种是枚举类,枚举类的底层实现其实也是内部类。枚举类确保每个类对象在全局是唯一的。所以保证它是单例,这个方法是最简单的。

简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例getInstance()的返回值是一个对象的引用,并不是一个新的实例,所以不要错误的理解成多个对象。单例模式实现起来也很容易,直接看demo吧

//懒汉写法(线程不安全)
public class Singleton{
private static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(){
if(Singleton == null){
singleton= new Singleton();
}
return singleton;
}
}
//懒汉式写法(线程安全)
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton  getInstance(){
    if(instance == null){
             instance = new Singleton();
    }
    return instance;
}
}
//饿汉式写法
public class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getinstance(){
    return instance;
    }
}
//静态内部类
public class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public  static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
//枚举
public enum Singleton{
INSTANCE;
public void whateverMethod(){
}
}

//双重校验锁
public class Singleton{
private volatile static Singleton singleton;
private Singleton (){}
public static  Singleton getSingleton(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}

}

观察者模式

对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

观察者模式UML图

看不懂图的人端着小板凳到这里来,给你举个栗子:假设有三个人,小美(女,22),小王和小李。小美很漂亮,小王和小李是两个程序猿,时刻关注着小美的一举一动。有一天,小美说了一句:“谁来陪我打游戏啊。”这句话被小王和小李听到了,结果乐坏了,蹭蹭蹭,没一会儿,小王就冲到小美家门口了,在这里,小美是被观察者,小王和小李是观察者,被观察者发出一条信息,然后观察者们进行相应的处理,看代码:

public interface Person {  
//小王和小李通过这个接口可以接收到小美发过来的消息   
void getMessage(String s);
}

这个接口相当于小王和小李的电话号码,小美发送通知的时候就会拨打getMessage这个电话,拨打电话就是调用接口,看不懂没关系,先往下看

public class LaoWang implements Person {   
private String name = "小王";   
public LaoWang() {   }   
@Override   
public void getMessage(String s) {       
System.out.println(name + "接到了小美打过来的电话,电话内容是:" + s);   
        }
}
public class LaoLi implements Person {   
private String name = "小李";   
public LaoLi() {   }   
@Override   
public void getMessage(String s) {       
System.out.println(name + "接到了小美打过来的电话,电话内容是:->" + s);   
       }
}

代码很简单,我们再看看小美的代码:


public class XiaoMei {   
List list = new ArrayList();    
public XiaoMei(){    }    
public void addPerson(Person person){       
list.add(person);    
}    
//遍历list,把自己的通知发送给所有暗恋自己的人    
public void notifyPerson() {        
for(Person person:list){            
person.getMessage("你们过来吧,谁先过来谁就能陪我一起玩儿游戏!");        
         }    
    }
}

我们写一个测试类来看一下结果对不对

public class Test {  
public static void main(String[] args) {       
XiaoMei xiao_mei = new XiaoMei();       
LaoWang lao_wang = new LaoWang();       
LaoLi lao_li = new LaoLi();       //小王和小李在小美那里都注册了一下       xiao_mei.addPerson(lao_wang);      
xiao_mei.addPerson(lao_li);       //小美向小王和小李发送通知
xiao_mei.notifyPerson();  
}
}

装饰者模式

对已有的业务逻辑进一步的封装,使其增加额外的功能,如Java中的IO流就使用了装饰者模式,用户在使用的时候,可以任意组装,达到自己想要的效果。 举个栗子,我想吃三明治,首先我需要一根大大的香肠,我喜欢吃奶油,在香肠上面加一点奶油,再放一点蔬菜,最后再用两片面包夹一下,很丰盛的一顿午饭,营养又健康。(ps:不知道上海哪里有卖好吃的三明治的,求推荐~)那我们应该怎么来写代码呢? 首先,我们需要写一个Food类,让其他所有食物都来继承这个类,看代码:


public class Food {   
private String food_name; 
public Food() {   }  
public Food(String food_name) {  
this.food_name = food_name;  
}  
public String make() { 
return food_name;  
};
}

然后我们写几个子类继承它:


//面包类
public class Bread extends Food {   
private Food basic_food;  
public Bread(Food basic_food) {       
this.basic_food = basic_food;   
}   
public String make() {       
return basic_food.make()+"+面包";  
}
}
//奶油类
public class Cream extends Food {   
private Food basic_food;   
public Cream(Food basic_food) {       
this.basic_food = basic_food;   
}   
public String make() {       
return basic_food.make()+"+奶油";   
}
}
//蔬菜类
public class Vegetable extends Food {   
private Food basic_food;   
public Vegetable(Food basic_food) {       
this.basic_food = basic_food;   }   
public String make() {       
return basic_food.make()+"+蔬菜";  
}
}

这几个类都是差不多的,构造方法传入一个Food类型的参数,然后在make方法中加入一些自己的逻辑,如果你还是看不懂为什么这么写,不急,你看看我的Test类是怎么写的,一看你就明白了


public class Test {   
public static void main(String[] args) {       
Food food = new Bread(new Vegetable(new Cream(new Food("香肠"))));
System.out.println(food.make());   
}
}

一层一层封装,我们从里往外看:最里面我new了一个香肠,在香肠的外面我包裹了一层奶油,在奶油的外面我又加了一层蔬菜,最外面我放的是面包,是不是很形象

我们看看运行结果吧

运行结果

适配器模式

将两种完全不同的事物联系到一起,就像现实生活中的变压器。假设一个手机充电器需要的电压是20V,但是正常的电压是220V,这时候就需要一个变压器,将220V的电压转换成20V的电压,这样,变压器就将20V的电压和手机联系起来了。


public class Test { 
public static void main(String[] args) {       
Phone phone = new Phone();       
VoltageAdapter adapter = new VoltageAdapter();       
phone.setAdapter(adapter);       
phone.charge();   
}
}
// 手机类
class Phone {   
public static final int V = 220;// 正常电压220v,是一个常量   
private VoltageAdapter adapter;   // 充电   
public void charge() {       
adapter.changeVoltage();  
}   
public void setAdapter(VoltageAdapter adapter) {       
this.adapter = adapter;   
}
}
// 变压器
class VoltageAdapter {   
// 改变电压的功能   
public void changeVoltage() {       
System.out.println("正在充电...");       
System.out.println("原始电压:" + Phone.V + "V");       
System.out.println("经过变压器转换之后的电压:" + (Phone.V - 200) + "V");   
}
}
image.png

代理模式(proxy)

有两种,静态代理和动态代理。用生活中的例子说话:
到了一定的年龄,我们就要结婚,结婚是一件很麻烦的事情,(包括那些被父母催婚的)。有钱的家庭可能会找司仪来主持婚礼,显得热闹,洋气~好了,现在婚庆公司的生意来了,我们只需要给钱,婚庆公司就会帮我们安排一整套结婚的流程。整个流程大概是这样的:家里人催婚->男女双方家庭商定结婚的黄道即日->找一家靠谱的婚庆公司->在约定的时间举行结婚仪式->结婚完毕
婚庆公司打算怎么安排婚礼的节目,在婚礼完毕以后婚庆公司会做什么,我们一概不知。。。别担心,不是黑中介,我们只要把钱给人家,人家会把事情给我们做好。所以,这里的婚庆公司相当于代理角色,现在明白什么是代理角色了吧。
代码实现请看:


//代理接口
public interface ProxyInterface {
//需要代理的是结婚这件事,如果还有其他事情需要代理,比如吃饭睡觉上厕所,也可以写
void marry();
//代理吃饭(自己的饭,让别人吃去吧)
//void eat();
//代理拉屎,自己的屎,让别人拉去吧
//void shit();}

好了,我们看看婚庆公司的代码:


public class WeddingCompany implements ProxyInterface {
private ProxyInterface proxyInterface;
public WeddingCompany(ProxyInterface proxyInterface) { 
this.proxyInterface = proxyInterface;
}
@Override
public void marry() { 
System.out.println("我们是婚庆公司的"); 
System.out.println("我们在做结婚前的准备工作"); 
System.out.println("节目彩排..."); 
System.out.println("礼物购买..."); 
System.out.println("工作人员分工..."); 
System.out.println("可以开始结婚了"); 
proxyInterface.marry(); 
System.out.println("结婚完毕,我们需要做后续处理,你们可以回家了,其余的事情我们公司来做");
}
}

看到没有,婚庆公司需要做的事情很多,我们再看看结婚家庭的代码:

public class NormalHome implements ProxyInterface{
@Override
public void marry() { 
System.out.println("我们结婚啦~");
}
}

这个已经很明显了,结婚家庭只需要结婚,而婚庆公司要包揽一切,前前后后的事情都是婚庆公司来做,听说现在婚庆公司很赚钱的,这就是原因,干的活多,能不赚钱吗?

来看看测试类代码:

public class Test {
public static void main(String[] args) { 
ProxyInterface proxyInterface = new WeddingCompany(new NormalHome()); 
proxyInterface.marry();
}
}

运行结果如下:


image.png

这就是静态代理,动态代理跟java反射有关系

Spring的AOP就是通过动态代理模式实现的(有接口时JDK动态代理实现,无接口时cglib实现)
ps:以下做个补充
原型模式

一般通过一个实例进行克隆从而获得更多同一原型的实例。使用实例的clone方法即可完成。

建造者模式

建造者模式中有一个概念叫做链式调用,链式调用为一个类的实例化提供便利,一般提供系列的方法进行实例化,实际上就是将set方法改造一下,将原本返回为空的set方法改为返回this实例,从而实现链式调用。
建造者模式在此基础上加入了builder方法,提供给外部进行调用,同样使用链式调用来完成参数注入。

结构型模式

前面创建型模式介绍了创建对象的一些设计模式。

这节介绍的结构型模式旨在通过改变代码结构来达到解耦的目的,使得我们的代码容易维护和扩展。

桥接模式

有点复杂

适配器模式

适配器模式用于将两个不同的类进行适配。

适配器模式和代理模式的异同

比较这两种模式,其实是比较对象适配器模式和代理模式,在代码结构上,它们很相似,都需要一个具体的实现类的实例。但是它们的目的不一样,代理模式做的是增强原方法的活;
适配器做的是适配的活,为的是提供“把鸡包装成鸭,然后当做鸭来使用”,而鸡和鸭它们之间原本没有继承关系。适配器模式可以分为类适配器,对象适配器等。类适配器通过继承父类就可以把自己适配成父类了。而对象适配器则需要把对象传入另一个对象的构造方法中,以便进行包装。

享元模式

享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,在享元池中保存该新增对象。

代理模式

我们发现没有,代理模式说白了就是做“方法包装”或“方法增强”。
在面向切面编程中,算了还是不要吹捧这个名词了,在 AOP中,其实就是动态代理的过程。比如Spring中,我们自己不定义代理类,但Spring会帮我们动态来定义代理,然后把我们定义在@Before、@After、@Around中的代码逻辑动态添加到代理中。

外观模式

外观模式一般封装具体的实现细节,为用户提供一个更加简单的接口。
通过一个方法调用就可以获取需要的内容。

组合模式

组合模式用于表示具有层次结构的数据,使得我们对单个对象和组合对象的访问具有一致性。
直接看一个例子吧,每个员工都有姓名、部门、薪水这些属性,同时还有下属员工集合(虽然可能集合为空),而下属员工和自己的结构是一样的,也有姓名、部门这些属性,同时也有他们的下属员工集合。

class Employee{
private String name;
private String dept;
private int salary;
private List subordinates;// 下属
}

装饰者模式

装饰者模式把每个增强类都继承最高级父类。然后需要功能增强时把类实例传入增强类即可,然后增强类在使用时就可以增强原有类的功能了。
和代理模式不同的是,装饰者模式每个装饰类都继承父类,并且可以进行多级封装。

行为型模式

行为型模式关注的是各个类之间的相互作用,将职责划分清楚,使得我们的代码更加地清晰。

策略模式

策略模式一般把一个策略作为一个类,并且在需要指定策略的时候传入实例,于是我们可以在需要使用算法的地方传入指定算法。

命令模式

命令模式一般分为命令发起者,命令以及命令接受者三个角色。
命令发起者在使用时需要注入命令实例。然后执行命令调用。
命令调用实际上会调用命令接收者的方法进行实际调用。
比如遥控器按钮相当于一条命令,点击按钮时命令运行,自动调用电视机提供的方法即可。

模板方法模式

模板方法一般指提供了一个方法模板,并且其中有部分实现类和部分抽象类,并且规定了执行顺序。
实现类是模板提供好的方法。而抽象类则需要用户自行实现。
模板方法规定了一个模板中方法的执行顺序,非常适合一些开发框架,于是模板方法也广泛运用在开源框架中。

观察者模式和事件监听机制

观察者模式一般用于订阅者和消息发布者之间的数据订阅。
一般分为观察者和主题,观察者订阅主题,把实例注册到主题维护的观察者列表上。
而主题更新数据时自动把数据推给观察者或者通知观察者数据已经更新。
但是由于这样的方式消息推送耦合关系比较紧。并且很难在不打开数据的情况下知道数据类型是什么。
知道后来为了使数据格式更加灵活,使用了事件和事件监听器的模式,事件包装的事件类型和事件数据,从主题和观察者中解耦。
主题当事件发生时,触发该事件的所有监听器,把该事件通过监听器列表发给每个监听器,监听得到事件以后,首先根据自己支持处理的事件类型中找到对应的事件处理器,再用处理器处理对应事件。

责任链模式

责任链通常需要先建立一个单向链表,然后调用方只需要调用头部节点就可以了,后面会自动流转下去。
比如流程审批就是一个很好的例子,只要终端用户提交申请,根据申请的内容信息,自动建立一条责任链,然后就可以开始流转了。

你可能感兴趣的:(常用到的设计模式)