20220906-重要的几种开发设计模式

重要的几种开发设计模式                      

1,代理模式
代理模式和日常生活中的代理是一个意思,即创建一个代理类,代替真实的业务类完成核心的业务场景。代理模式可以隐藏真实的业务类细节,代理类甚至还可以在核心业务开始和结束时,执行一些记录操作,类似于我们经常使用的
AOP。
举个例子
我们有个加载图片并展示的功能,不希望把加载图片的细节对外展示,并且希望统计每次加载图片一共花费了多少时间,这个时候我们可以考虑使用代理模式。
先看一下UML图:
 
0
再来看一下具体代码是怎么实现的:
public interface Image { 
  void display();
}

 

真正的完成核心业务的类
public class RealImage implements Image {// 实现图片接口
        private String fileName;
        public RealImage(String fileName) throws InterruptedException {
                this.fileName = fileName;
                loadFromDisk(fileName);
        }
        @Override
        public void display() {
                System.out.println("Displaying " + fileName);
        }
        private void loadFromDisk(String fileName) throws InterruptedException {
                System.out.println("Loading " + fileName);
                Thread.sleep((long) (Math.random()*100));
        }
}

 

代理类,除了完成主要业务逻辑,还增加了加载图片的时间统计功能。
public class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;
    public ProxyImage(String fileName) {
            this.fileName = fileName;
    }
    @Override
    public void display() throws InterruptedException {
            if (realImage == null){
                long startTime = System.currentTimeMillis();
                realImage = new RealImage(fileName); 
                long endTime = System.currentTimeMillis();
                long spendTime = endTime - startTime;
                System.out.println("加载图片花费了: " + spendTime );
            }
            realImage.display();
    }
}

 

看一下客户端如何使用
public class ProxyPatternDemo {
    public static void main(String[] args) throws InterruptedException {
        //使用代理类
        Image image = new ProxyImage("test_10mb.jpg");        //图片被加载并统计加载时间,所有细节全部被隐藏
        image.display();
        System.out.println("");
    }
}

 

总结
代理模式是结构型模式之一,日常使用的频率比较高,最典型的应用就是AOP,日志记录等功能。
2,工厂模式
2.1 简单工厂模式
简单工厂严格的说并不是GOF里面提到的标准的23个设计模式之一。但是日常开发中经常会用到,特别是比较小的系统。简单工厂模式就是通过一个工厂类的工厂方法创建相应的对象。
举个例子
我们希望创建三种不同类型的图表,并进行数据展示,这个时候可以考虑使用简单工厂模式。
先来看一下UML图:
 
0
来看一下代码如何实现。
先来定义一个抽象类 Chart
 
public abstract class Chart{
    abstract void diplay();
}

 

分别定义三种不同的图表,均继承自Chart,这里因为篇幅原因只列出HistogramChart类
 
//定义柱状图图表类,体现单一职责
public class HistogramChart extends Chart {
    @Override
    void display() {
        //显示柱状图
    }
}

 

定义一个工厂类,通过静态工厂方法,可以创建三种不同类型的图表。
 
public class ChartFactory {
    //静态工厂方法
    public static Chart createChart(String chartType){
        Chart chart = null;

        if (chartType.equalsIgnoreCase("histogram")){
            //创建柱状图
            chart = new HistogramChart();
        }else if (chartType.equalsIgnoreCase("pie")){
            //创建饼状图
            chart = new PieChart();
        }else if (chartType.equalsIgnoreCase("line")){
            //创建折线图
            chart = new LineChart();
        }

        return chart;
    }
}

 

来看一下客户端如何使用
 
public class Client {
    public static void main(String[] args){
        //通过工厂类得到想要的图表
        Chart chart = ChartFactory.createChart("pie");
        chart.display();
    }
}

 

总结
简单工厂模式属于创建型模式之一,用于系统比较简单,创建的种类比较少的情况,因为创建的种类如果太多的话,工厂类里面的静态工厂方法if else就太混乱了,而且所要创建的具体类和工厂类耦合太紧密。
2.2 工厂模式
工厂模式属于创建型设计模式。是日常使用比较多的一种设计模式。比如Logback,Spring等框架中都大量使用该设计模式。
简单工厂模式虽然在简单系统可以用一用,但是因为工厂类本身包含太多业务逻辑,而且如果想增加新的图表类型,需要修改工厂类等缺点。所以我们真正生产系统更多的使用工厂模式。
工厂模式为每一个类都定义一个单独的工厂类,该工厂类只负责创建单一的产品。
举个例子
通过工厂模式实现日志的创建,为不同种类的Logger分别创建不同类型的工厂。
来看一下UML图:
 
0
 
来看一下代码如何实现
先来定义一个Logger接口,以及LoggerFactory接口
public interface Logger {
    void writeLogger();
}

 

public interface LoggerFactory{
    Logger createLogger();
}

 

分别定义FileLogger和FileLoggerFactory
public class FileLogger implements Logger {
    @Override
    public void writeLogger() {
    System.out.println("File logger");    
    }
}

 

public class FileLoggerFactory implements LoggerFactory {    
    @Override    
    public Logger createLogger() {        
        //不同Logger的个性初始化工作        
        Logger logger =  new FileLogger();        
        return logger;    
    }
}

 

如果想增加新的Logger直接实现Logger和LoggerFactory就好了,看一下客户端如何使用
public class Client {    
    public static void main(String[] args){        
        Logger logger;        
        LoggerFactory loggerFactory;        l
        loggerFactory = new FileLoggerFactory();        
        logger = loggerFactory.createLogger();        
        logger.writeLogger();    
    }
}

 

总结
工厂模式是创建型模式之一,日常使用比较多,每类产品都有对应的一个工厂类。工厂模式把简单工厂的内部逻辑迁移到了客户端,当需要对功能扩展时,只需要修改客户端就OK了。
2.3 抽象工厂模式
抽象工厂提供了一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。简单来说是为需要创建多个相关联产品的场景提供了解决思路。
举个例子
很多软件都支持配置不同的颜色的主题,当切换主题时,所有按钮样式和颜色都会统一切换。
假设我们的软件只有三类按钮,分别是Button, TextField,以及ComboBox,并且我们的软件只提供两类主题,即红色主题和绿色主题。
来看一下UML :
 
0
 
再来看一下具体代码如何实现。
先来创建一个SkinFactory接口
public interface SkinFactory {    
    Button createButton();    
    TextField createTextField();    
    ComboBox createComboBox();
}

 

通过实现SkinFactory实现绿色主题工厂
/
//红色主题工厂
public class RedSkinFactory implements SkinFactory {    
    @Override    
    public Button createButton() {        
        return new RedButton();    
    }    
    @Override    
    public TextField createTextField() {        
        return new RedTextField();    
    }    
    @Override    
    public ComboBox createComboBox() {        
        return new RedComboBox();    
    }
}

 

红色主题工厂类类似,因为篇幅原因不在单独列出。
来看一下客户端如何使用
public class Client {    
    public static void main(String[] args){        
        //通过切换不同工厂,可以达到所有按钮颜色都变换的效果        
        SkinFactory skinFactory = new RedSkinFactory();        
        Button button = skinFactory.createButton();        
        ComboBox comboBox = skinFactory.createComboBox();        
        TextField textField = skinFactory.createTextField();        
        button.display();        
        comboBox.display();        
        textField.display();    
    }
}

 

总结
抽象工厂是创建模式之一,抽象工厂适合创建一系列有相关性的多个产品,虽然抽象工厂在扩展一个主题的按钮时灵活方便,但是也有一定缺点, 最大缺点就是想单独增加一个不属于任何主题的按钮时比较麻烦,比如上面的例子中,如果想增加一个关闭按钮,这个按钮即不属于红色主题也不属于绿主题,那么改动起来就比较复杂。
工厂模式三兄弟:简单工厂适合逻辑简单,创建产品数量很少的场景,工厂模式适合创建独立的产品,抽象工厂适合创建一系列有相关性的多个产品。
3 单例模式
单例模式顾名思义,就是保证系统中一个类只有一个唯一的对象实例。实际开发过程中经常会碰到这种需求。
单例模式有两种实现方式,一种称为饿汉式,一种称为懒汉式。
先来看一下UML图:
 
0
 
饿汉式
之所以称为饿汉,是因为该种实现方式是在类被加载的时候就初始化对象,而且是通过JVM的类加载机制保证系统中只有一个类实例。
先看饿汉式的完整代码
public class SingleObject {    
    //create an object  of SingleObject    
    private static SingleObject instance = new SingleObject();    
    //make the constuctor private so that this class cannot be instantiated    
    private SingleObject(){}    
    public static SingleObject getInstance(){        
        return instance;    
    }    
    public void showMessage(){       
        System.out.println("I'm eager");    
    }
}

 

饿汉式的关键代码在第一行,通过static关键字修饰了instance变量了,这样在类被加载的时候就会初始化SingleObject。
懒汉式
所谓的懒汉式就是在需要的时候在创建对象,懒汉式比较复杂,需要通过锁加双重检查机制来保障多线程环境下,只有一个实例被创建。完整代码如下
public class LazySingleObject {    
    private volatile static LazySingleObject instance = null;    
    private LazySingleObject() {}    
    public static LazySingleObject getInstance(){        
        //第一重检查        
        if (instance == null){            
            synchronized (LazySingleObject.class){               
                //第二重检查                
                if (instance == null){                    
                    instance = new LazySingleObject();                
                }            
            }        
        }        
        return instance;    
    }        
    public void showMessage(){        
        System.out.println("I'm lazy");    
    }    
}

 

这样使用单例模式
SingleObject object = SingleObject.getInstance();//show the messageobject.showMessage();

 

总结
单例模式也是创建型模式之一,实现该模式的时候都要把实例变量设置为静态的,其次构造函数都要变成私有的,防止在外部被引用并创建对象。饿汉式实现起来比较容易,但是初始化的实例对象比较耗时和占用资源的话,在系统启动时会比较浪费时间和资源;相反懒汉式则是在需要的时候进行初始化,不会占用系统启动资源,但是实现起来比较麻烦,特别是在多线程的场景下,除非有特殊要求,否则建议使用饿汉式。
4 委派模式
定义
委派模式,是一种面向对象的设计模式,是一种特殊的静态代理,可以理解为全权代理,代理模式注重的是对原始被代理的增强,委派注重的是交给委派对象拿到委派之后的结果。委派属于行为型设计模式。
 
委派的角色(类似中介)
  • 抽象任务角色(Task)定义一个抽象解耦他有若干实现类
 
  • 委派者角色(Delegate): 负责在各个具体角色实例之间做出角色,判断并调用具体实现方法
 
  • 具体任务角色(Concrete): 真正执行任务的角色
 
/
//具体任务类抽象
public interface IEmployee {
    void doing(String task);
}
//具体任务类A
public class EmployeeA implements IEmployee{
    private String role = "程序员";
    @Override
    public void doing(String task) {
        System.out.println("开始编程");
    }
}
//具体任务类B
public class EmployeeB implements IEmployee{
    private String role = "设计师";
    @Override
    public void doing(String task) {
        System.out.println("开始画稿");
    }
}
//委派角色 负载分发任务 和 执行
public class Leader implements IEmployee{
    private Map<String, IEmployee> employeeMap = new HashMap<>();
    public Leader(){
        employeeMap.put("coding", new EmployeeA());
        employeeMap.put("design", new EmployeeB());
    }
​
    //这里使用策略模式,拿到具体的一个方案,然后执行
    @Override
    public void doing(String task) {
        if(!employeeMap.containsKey(task)) {
            System.out.println(task + ",sorry能力有限另寻高明");
        }else{
            employeeMap.get(task).doing(task);
        }
    }
}
//调用者持有委派角色,直接从委派角色拿到结果,对执行过程完全透明
public class Boss {
​
    public void command(String task, Leader leader){
        leader.doing(task);
    }
​
    public static void main(String[] args) {
        Boss boss = new Boss();
        Leader leader = new Leader();
        boss.command("coding", leader);
    }
}

 

委派模式优缺点:
优点:将大的任务通过任务职责细分到不同的委派角色 每个委派角色负责执行各个子任务,边界更明确
缺点: 代码实现更复杂,类的关系也更复杂,任务复杂的情况下可能出现多重委派,导致代码结构紊乱
5 策略模式
策略模式常用于一个对象的行为在运行时有多个不同的策略和算法。
举个例子
可以通过实现一个加、减、乘三种不用策略的运算,通过该示例演示策略模式。
先来看一下UML:
 
0
 
来看一下代码如何实现
先来定义一个策略接口
public interface Strategy {    
    int doOperation(int num1, int num2);
}

 

分别定义具体的加、减、乘策略,实现策略接口
定义加法策略
public class OperationAdd implements Strategy {    
    @Override    
    public int doOperation(int num1, int num2) {        
        return num1 + num2;    
    }
}

 

减法和乘法策略大同小异,篇幅原因掠过。
定义一个Context方法,根据不同策略执行不同的行为
public class Context {    
    private Strategy strategy;    
    public Context(Strategy strategy){        
        this.strategy = strategy;    
    }    //执行不同的策略    
    public int executeStrategy(int num1, int num2){        
        return strategy.doOperation(num1,num2);    
    }
}

 

来看一下客户端如何使用
public class StrategyPatternDemo {    
    public static void main(String[] args){        
        Context context = new Context(new OperationAdd());        
        System.out.println("10 + 5 = " + context.executeStrategy(10,5));        
        context = new Context(new OperationSubstract());        
        System.out.println("10 - 5 = " + context.executeStrategy(10,5));        
        context = new Context(new OperationMultiply());        
        System.out.println("10 * 5 = " + context.executeStrategy(10,5));    
    }
}

 

总结
策略模式是行为模式之一,策略模式是用来封装算法的,实践过程中,不仅可以封装算法,任何类型的规则都可以封装。
6 原型模式
原型模式其实很简单,就是利用Java提供的Clonable接口,实现对象的复制。之所以称之为原型模式,是因为我们复制的时候要有一个原型或者模型。该模式经常用于所要创建的对象比较复杂的时候,我们可以直接基于现有的对象复制一个出来直接使用。使用原型模式的时候要考虑Java的深复制和浅复制,要进行深复制的时候建议通过序列化的方式实现。
举个例子
通过一个图形缓存器来演示原型模式的使用。
先来看一下UML :
 
0
 
下面给出一个简单的图形缓存器来演示原型模式(浅复制)。
public abstract class Shape implements Cloneable {    
    private String id;    
    protected String type;    
    abstract void draw();    
    //篇幅原因,此处省略get,set方法    
    //注意这段代码,关键就都在这里了。    
    @Override    
    protected Object clone() {        
        Object clone = null;        
        try{            
            clone = super.clone();        
        }catch (CloneNotSupportedException e){            
            e.printStackTrace();        
        }        
        return clone;    
    }
}

 

定义一个Circle实现Shape
public class Circle extends Shape {    
    public Circle(){        
        type = "Circle";    
    }    
    @Override    
    void draw() {        
        System.out.println("Inside Circle::draw() method");    
    }
}

 

定义一个图形缓存器,里面存储创建好的图形,当客户端需要一个新的图形时,直接从图形缓存器复制一个出来。
public class ShapeCache {    
    private static Map<String,Shape> shapeMap = new HashMap<>();    
    public static Shape getShape(String shapeId){        
        Shape cacheShape = shapeMap.get(shapeId);       
        // 通过clone的方式返回一个和现有Circle一模一样的新Circle        
        return (Shape) cacheShape.clone();    
    }    
    //创建一个新的Circle,并放进缓存器    
    public static void loadCache(){        
        Circle circle = new Circle();        
        circle.setId("1");        
        shapeMap.put(circle.getId(),circle);    
    }
}

 

客户端这样使用
public class PrototypePatternDemo {    
    public static void main(String[] args){        
        //创建新的Cirlce并放进缓存器        
        ShapeCache.loadCache();        
        //通过clone的方式拿到一个和原来一样的新Cirlce        
        Shape cloneShape = ShapeCache.getShape("1");       
        System.out.println("Shape : " + cloneShape.getType() + "ID : " + cloneShape.getId() );    
    }
}

 

总结
原型模式很简单,是创建型模式之一,只要记住原型模式就是利用Cloneable实现对象的复制就够了。
7 模板模式
模板方法通常用来定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法可以在不改变一个算法的结构的情况下,就可以重新定义该算法的某些特定步骤。
举个例子
我们来实现一个竞技类的电子游戏,大部分的竞技游戏的流程基本上都包含如下过程 初始化游戏,启动游戏,游戏结束这几个步骤,但是不同游戏的初始化和启动方法不一样,我们可以考虑使用模板方法来实现。
来看一下UML:
 
0
 
看一下具体代码如何实现
先来定义一个抽象的游戏类Game,里面定义模板方法
public abstract class Game {    
    abstract void init();    
    abstract void startPlay();    
    abstract void endPlay();    
    //template method    
    public final void play(){        
        init();        
        startPlay();        
        endPlay();    
    }
}

 

分别定义表示足球游戏的类Football和表示篮球游戏的类BasketBall;两个类全部继承自抽象类Game
public class Football extends Game {    
    @Override    
    void init() {        
        System.out.println("Football Game init");    
    }    
    @Override    
    void startPlay() {        
        System.out.println("Football Game Start");    
    }    
    @Override    
    void endPlay() {        
        System.out.println("Football Game End");    
    }
}

 

BasketBall和Football类似,因篇幅原因不再列出。
来看一下客户端如何使用
public class TemplatePatternDemo {    
    public static void main(String[] args){       
        Game basketBall = new BasketBall();        
        basketBall.play();       
        Game football = new Football();        
        football.play();    
    }
}

 

总结
模板方法是行为模式之一,模板方法通过把不变的行为搬移到父类,去除子类中的重复代码,使其代码结构更精简。
posted @ 2022-09-06 18:02  爱吃猫的鱼9527  阅读(36)  评论(0)    收藏  举报