设计模式③ :生成实例

文章目录

  • 一、前言
  • 二、Singleton 模式
    • 1. 介绍
    • 2. 应用
    • 3. 总结
  • 三、Prototype 模式
    • 1. 介绍
    • 2. 应用
    • 3. 总结
  • 四、Builder 模式
    • 1. 介绍
    • 2. 应用
    • 3. 总结
  • 五、Abstract Factory 模式
    • 1. 介绍
    • 2. 应用
    • 3. 总结
  • 参考内容

一、前言

有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著)。该系列文章可随意转载。

二、Singleton 模式

Singleton 模式 :只有一个实例

1. 介绍

何为Singleton 模式 :

  • 想确保任何情况下都绝对只有一个实例
  • 想在程序上表现出“只存在一个实例”

像这种确保只生成一个实例的模式被称为Singleton 模式。


Singleton 模式登场的角色:

  • Singleton :在 Singleton 模式中只有这一个角色。Singleton 角色中有一个返回唯一实例的 static 方法,该方法总是会返回同一个实例。

类图如下:
在这里插入图片描述


Demo 如下:

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

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

2. 应用

  • Spring 容器中 bean 默认的就是单例的。实际上是每个 Bean在创建后都会注册到一个 Map集合中。但实际上,Spring中的单例模式完成了保证一个类仅有一个实例,但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 目前的单例模式除了自定义一些工具类或者资源类之外,大部分情况下都是通过将 Bean 实例注入到Spring容器中,依赖于Spring的特性来保证实例的唯一性 。

3. 总结

单例模式有多种实现 饱汉式、饿汉式、DCL等等,这里不做过多赘述,如有需要,可以参考:设计模式-单例模式介绍和使用场景及Spring单例模式的使用


相关设计模式:以下设计模式中,多数情况下都只会生成一个实例。

  • AbstractFactory模式
  • Builder 模式
  • Facade 模式
  • Prototype 模式

三、Prototype 模式

Prototype 模式 :通过复制生成实例

1. 介绍

在开发过程中会存在 在不指定类名的前提下生成实例 的需求,如:

  1. 对象种类繁多,无法将它们整合到一个类中 :该种情况是需要处理的对象太多,如果将它们分别作为一个类,必须要编写很多个类文件。
  2. 难以根据类生成实例时 :该种情况是生成实例的过程太过复杂,很难根据类来生成实例。
  3. 想解耦框架与生成的实例时 :该种情况是想让生成实例的框架不依赖具体的类。这时,不能指定类名来生成实例,而要实现“注册”一个“原型”实例,然后通过复制该实例来生成新的实例。

Prototype 模式中登场的角色

  • Prototype(原型):该角色负责定义用于复制现有实例来生成新实例的方法。在下面Demo中由 Product 接口扮演该角色
  • ConcretePrototype(具体的原型):该角色负责实现复制现有实例并生成新的实例方法。在下面Demo中由 MessageBox 和 UnderlinePen 接口扮演该角色
  • Client(使用者):该角色负责使用复制实例的方法生成新的实例。在下面Demo中由 Manger 接口扮演该角色

类图如下:
在这里插入图片描述


Demo 如下:

  • Manager : 调用 createClone 方法复制实例的类
  • Product :声明了抽象方法 use 和 createClone 的接口
  • MessageBox :将字符串放入方框中并使其显示出来的类。Product 的子类
  • UnderlinePen :给字符串加上下划线并使其显示出来的类。Product 的子类

public class Manager {
    private Map<String, Product> registeredProducts = Maps.newHashMap();

    /**
     * 注册 Product
     * @param name
     * @param product
     */
    public void register(String name, Product product) {
        registeredProducts.put(name, product);
    }

    /**
     * 创建 Product
     * @param productName
     * @return
     */
    public Product create(String productName) {
        return registeredProducts.get(productName).createClone();
    }
}

// Product 接口
public interface Product extends Cloneable {
    /**
     * 使用该 Product
     * @param msg
     */
    void user(String msg);

    /**
     * 通过 clone 创建出一个 Product实例
     * @return
     */
    Product createClone();
}


public class MessageBox implements Product {
    private char msgChar;

    public MessageBox(char msgChar) {
        this.msgChar = msgChar;
    }

    @Override
    public void user(String msg) {
        for (int i = 0; i < msg.length() + 4; i++) {
            System.out.print(msgChar);
        }
        System.out.println();
        System.out.println(msgChar + " " + msg + " " + msgChar);
        for (int i = 0; i < msg.length() + 4; i++) {
            System.out.print(msgChar);
        }
        System.out.println();
    }

    @Override
    public Product createClone() {
        try {
            return (Product) clone();
        } catch (Exception e) {
            return null;
        }
    }
}


public class UnderlinePen implements Product {

    private char msgChar;

    public UnderlinePen(char msgChar) {
        this.msgChar = msgChar;
    }

    @Override
    public void user(String msg) {
        System.out.println(msg);
        for (int i = 0; i < msg.length(); i++) {
            System.out.print(msgChar);
        }
        System.out.println();
    }

    @Override
    public Product createClone() {
        try {
            return (Product) clone();
        } catch (Exception e) {
            return null;
        }
    }
}

// 测试用main 方法
public class DemoMain {
    public static void main(String[] args) {
        Manager manager = new Manager();
        UnderlinePen underlinePen = new UnderlinePen('~');
        MessageBox messageBox = new MessageBox('*');

        manager.register("underlinePen", underlinePen);
        manager.register("messageBox", messageBox);

        manager.create("underlinePen").user("Hello World");

        System.out.println();

        manager.create("messageBox").user("Hello World");

    }
}

Main 方法执行后输出如下

Hello World
~~~~~~~~~~~

***************
* Hello World *
***************

2. 应用

  • 如利用 org.springframework.beans.BeanUtils#copyProperties 方法对 Bean 进行拷贝复制时则是通过已有 Bean 拷贝复制出新的 Bean。


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 在某项目中,当遇到异常情况时需要发送告警信息,告警信息多种多样,但是标题信息不同,这时使用 Prototype 模式来 拷贝告警信息的基础信息。(不过实际上即是不依靠 Prototype 模式也可以实现该需求,不过这里通过 WarnMsgManager 的存在使其与 WarnMsg 解耦)如下:

    // 告警消息基础类
    @Slf4j
    @Data
    public class WarnMsg implements Cloneable {
        /**
         * 消息标题
         */
        private String title;
        /**
         * 消息内容
         */
        private String content;
        /**
         * 环境
         */
        private String env;
    
        /**
         * 需要的话可以重写该方法
         *
         * @return
         */
        public WarnMsg clone(String content) {
            try {
                final WarnMsg clone = (WarnMsg) clone();
                clone.setContent(content);
                return clone;
            } catch (Exception e) {
                log.warn("[告警信息][克隆失败]", e);
            }
            return null;
        }
    
    }
    
    // 上线告警
    public class OnlineWarnMsg extends WarnMsg {
        public OnlineWarnMsg() {
            setTitle("上线");
            setEnv("AAA");
        }
    }
    
    // 离线告警
    public class OfflineWarnMsg extends WarnMsg {
        /**
         * 设置消息的初始属性,假设这里有很多属性
         */
        public OfflineWarnMsg() {
            setTitle("下线");
            setEnv("AAA");
        }
    }
    
    // 告警消息管理类
    public class WarnMsgManager {
        /**
         * 通过 warnMsgMap 可以将 WarnMsg 与 WarnMsgManager 解耦
         */
        private Map<String, WarnMsg> warnMsgMap = Maps.newHashMap();
    
        /**
         * 注册消息类型
         * @param name
         * @param warnMsg
         */
        public void register(String name, WarnMsg warnMsg) {
            warnMsgMap.put(name, warnMsg);
        }
    
        /**
         * 创建对应消息类型
         * @param name
         * @return
         */
        public WarnMsg create(String name, String content) {
            return warnMsgMap.get(name).clone(content);
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 初始化数据
            WarnMsgManager warnMsgManager = new WarnMsgManager();
            warnMsgManager.register("offline", new OfflineWarnMsg());
            warnMsgManager.register("online", new OnlineWarnMsg());
            // 创建告警消息实例
            final WarnMsg online = warnMsgManager.create("online", "上线信息");
            final WarnMsg offline = warnMsgManager.create("online", "下线信息");
    
            System.out.println("offline = " + offline);
            System.out.println("online = " + online);
        }
    }
    

3. 总结

Prototype 模式可以用于 对象的构造成本比较高或是较为麻烦时,其是对象构建时需要频繁获取链接或锁的情况时,通过 clone 的效率会更高,否则可以使用new的形式。


相关设计模式

  • Flyweight 模式:使用 Prototype 模式可以生成一个与当前实例的状态完全相同的实例。而使用 Flyweight 模式可以在不同的地方使用同一个实例。
  • Memento 模式:使用 Prototype 模式可以生成一个与当前实例的状态完全相同的实例。而使用 Memento 模式可以保存当前实例的状态,实现快照和撤销功能。
  • Composite 模式以及 Decoratort 模式 :经常使用Composite 模式以及 Decoratort 模式 时,需要能够动态地创建复杂结构实例。这时可以使用 Prototype 模式,以方便生成实例。
  • Command 模式:想要复制 Command 模式中出现的命令时,可以使用 Prototype 模式。

扩展思路

  • 在 Java 对象中,我们可以通过 clone 方法来复制实例。但需要注意,如果需要调用 clone 方法,则对象需要实现 java.lang.Cloneable 接口(被复制对象的父类或父接口实现都可以)。另外 clone 方法的定义是在 java.lang.Object 中,并非在 java.lang.Cloneable 接口中, java.lang.Cloneable 接口仅仅用来标记 “可以使用 clone 方法”。
  • 实现了 java.lang.Cloneable 接口 调用 clone 方法进行复制时, clone 方法的返回值是复制出的新的实例(clone 方法内部所进行的处理是分配与要复制的实例同样大小的内存空间,接着将要复制的实例中的字段的值复制到所分配的内存空间中)
  • clone 方法所进行的只是浅拷贝,如果需要实现深拷贝则需要调用者自身重写。需要注意是 clone 在复制对象的时候并不会调用对象的构造函数,因此如果对象在构造函数中有一些特殊处理,则需要调用者自身去处理。

四、Builder 模式

Builder 模式 :组装复杂的实例

1. 介绍

Builder 模式中登场的角色

  • Builder (建造者):该角色负责定义用于生成实例的接口 ,其中定义了准备用于生成实例的方法。
  • ConcreteBuilder (具体的建造者):该角色负责实现 Builder 角色的接口的类,这里定义了在生成实例时实际被调用的方法,此外,该角色还定义了获取最终生成结果的方法。
  • Director(监工):该角色负责使用Builder 角色的接口来生成实例。它并不依赖于 ConcreteBuilder 角色。他只会调用在Builder 角色中定义的方法。
  • Client (使用者):该角色使用了Builder 模式。在示例程序中由 Main 类扮演该角色。

类图如下:在这里插入图片描述


Demo 如下:

// 抽象构建器类
public abstract class Builder {
    /**
     * 构建 title
     * @param title
     */
    public abstract void builderTitle(String title);

    /**
     * 构建 content
     * @param content
     */
    public abstract void builderContent(String content);

    /**
     * 构建结束 :可以做最后的整合或者一些必填参数的校验
     */
    public abstract void close();
}



// 使用 Builder 生成实例
public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    /**
     * 定义生成过程,并且可以做一些额外的操作
     */
    public void construct() {
        builder.builderTitle("title");
        builder.builderContent("content");
        builder.close();
    }
}


public class ConcreteBuilder extends Builder {
    private String fileContent = "";


    @Override
    public void builderTitle(String title) {
        fileContent += title + "\n";
    }

    @Override
    public void builderContent(String content) {
        fileContent += content;
    }

    @Override
    public void close() {
        // TODO : 可以用于校验一些必须填充的条件,或其他处理
    }


    public String getResult() {
        return fileContent;
    }
}

public class Main {
    public static void main(String[] args) {
        // 实际使用中会可能会由多种 Builder
        ConcreteBuilder concreteBuilder = new ConcreteBuilder();
        final Director director = new Director(concreteBuilder);
        director.construct();
        final String result = concreteBuilder.getResult();
        System.out.println(result);
    }
}

2. 应用

  • Spring Security 中的 HttpSecurity 的构建个人认为就是对复杂对象构建的应用,如下:
     @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 自定义登陆拦截器
            JwtLoginFilter jwtLoginFilter = new JwtLoginFilter();
            jwtLoginFilter.setAuthenticationManager(authenticationManagerBean());
            jwtLoginFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
            jwtLoginFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
    
            JwtTokenFilter jwtTokenFilter = new JwtTokenFilter();
    
            // 使用自定义验证实现器
            JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(userDetailsService, passwordEncoder);
    
            // 登陆验证信息
            http.authenticationProvider(jwtAuthenticationProvider)
                    .authorizeRequests()
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        @Override
                        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                            object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
                            object.setAccessDecisionManager(accessDecisionManager);
                            return object;
                        }
                    })
                    .anyRequest().authenticated()
                    .and()
                    .formLogin();
    
            // jwt 拦截器配置
            http.sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //禁用session
                    .and()
                    .csrf().disable()
                    .addFilterAt(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class) // 添加拦截器
                    .addFilterAfter(jwtTokenFilter, JwtLoginFilter.class);
    
            // 权限处理信息
            http.exceptionHandling()
                    //   用来解决认证过的用户访问无权限资源时的异常
                    .accessDeniedHandler(accessDeniedHandler)
                    // 用来解决匿名用户访问无权限资源时的异常
                    .authenticationEntryPoint(authenticationEntryPoint);
    
    
        }
    


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 在某项目中,某一个接口返回的数据结果集相同,但是其内部会根据 Region 的不同通过不同的途径去获取数据,最终构建出来一个完成的数据集,如果对象的构建过程非常耗时可以将对象的部分内容异步构建。这里可以使用Builder。如下:

    // 顶层抽象类,protected  防止子类滥用方法
    public abstract class Builder {
        protected abstract String buildTitle(String msg);
    
        protected abstract String buildAuthor(String msg);
    
        protected abstract String buildContent(String msg);
    
        protected abstract boolean close();
    }
    // 增加获取数据集的方法
    public abstract class DataBuilder extends Builder {
        /**
         * 构建并校验
         * @return
         */
        public String getResult() {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(buildTitle("")).append("\n");
            stringBuilder.append(buildAuthor("")).append("\n");
            stringBuilder.append(buildContent("")).append("\n");
            close();
            return stringBuilder.toString();
        }
    }
    
    // 一个数据来源的实现,简单实现
    public class OneDataBuilder extends DataBuilder {
        @Override
        public String buildTitle(String msg) {
            return "title";
        }
    
        @Override
        public String buildAuthor(String msg) {
            return "author";
        }
    
        @Override
        public String buildContent(String msg) {
            return "content";
        }
    
        @Override
        public boolean close() {
            // TODO : 检验数据或对数据做最后的处理
            return true;
        }
    }
    
    // main 方法调用
    public class Main {
        public static void main(String[] args) {
            // 实际应用会有多个 DataBuilder。
            DataBuilder dataBuilder = new OneDataBuilder();
            // 获取结果。不同的Region数据来源不同,最后拼接的数据集 result。
            // 在某些 Region 中,DataBuilder 不同部分的数据甚至可以可以开启异步来获取。最终在 getResult 方法中拼接
            final String result = dataBuilder.getResult();
            System.out.println("result = " + result);
        }
    }
    
    

3. 总结

  • 在示例程序中, Main 并不知道有没有调用 Builder ,他仅仅是调用了 Director 的 construct 方法,这样 Director 就会开始工作完成了文档的编写,而 Main 对此一无所知。而对于 Director 知道 Builder ,并调用Builder 的方法来完成文档编写。但是他并不知道具体是 Builder 的哪个子类,他仅仅知道这是个 Builder 类。这样就可以完成类子类的传递,无论是哪个 Builder 的子类都可以借用 Director 来完成方法调用。
  • Builder 模式 可以用于复杂对象的创建,尤其是多个实现的复杂对象,在设想中,设置可以将部分耗时以及与主体数据无太多关联的数据异步构建,最后通过 close 或者其他方法来等待所有同步和异步数据构建完成,提高对象的创建效率。

相关设计模式 :

  • Template Method 模式:在 Builder 模式中,Director 角色控制 Builder 角色。在 Template Method 模式中,父类控制子类。
  • Composite 模式:有些情况下 Builder 模式生成的实例构成了 Composite 模式。
  • Abstract Factory 模式 :Abstract Factory 模式 和 Builder 模式都用于生成复杂实例。
  • Facade 模式:在 Builder 模式中,Director 角色通过组合 Builder 角色中的复杂方法向外部提供可以简单生成实例的接口,Facade 模式中的 Facade 角色则是通过组合内部模块向外部提供可以简单调用的接口。

五、Abstract Factory 模式

Abstract Factory 模式 :将关联零件组装成产品

本部分内容推荐阅读 : 抽象工厂模式在spring源码中的应用,以下部分内容参考该文。

1. 介绍

抽象工厂会将抽象零件组装成抽象产品。也就是说,我们并不关心零件的具体实现,而是只关心接口。我们仅使用该接口欧将零件组装成为产品。

在 Template Method 模式和 Builder 模式中,子类这一层负责方法的具体实现。在 Abstract Factory 模式中也是一样的。在子类这一层中有具体的工厂,它负责将具体的零件组装成为具体的产品。


Abstract Factory 模式 登场的角色如下:

  • AbstractProduct (抽象产品):该角色负责定义 AbstractFactory角色所生成的抽象零件和产品的接口。
  • AbstractFactory(抽象工厂):该角色负责定义用于生成抽象产品的接口
  • Client(委托者):该角色仅会调用 AbstractFactory 角色和 AbstractProduct 角色的接口来进行工作,对于具体的零件、产品和工厂一无所知。
  • ConcreteProduct (具体产品):该角色负责实现 AbstractProduct 角色的接口。
  • ConcreteFactory (具体工厂):该角色负责实现 AbstractFactory 角色的接口。

类图如下:
在这里插入图片描述


Demo 如下:

// 抽象罐头工厂类
public abstract class CanFactory {

    /**
     * 获取工厂实例
     * @param className
     * @return
     * @throws Exception
     */
    public CanFactory getCanFactory(String className) throws Exception {
        final Class<?> factoryClass = Class.forName(className);
        return (CanFactory) factoryClass.newInstance();
    }

    /**
     * 包装苹果罐头
     */
    public abstract AppleCan packAppleCan();

    /**
     * 包装牛肉罐头
     */
    public abstract BeefCan packBeefCan();

}

// 互联网罐头工厂
public class InternetCanFactory extends CanFactory {
	// 包装苹果罐头
    @Override
    public AppleCan packAppleCan() {
        return new BaiduAppleCan();
    }
	// 包装牛肉贯通
    @Override
    public BeefCan packBeefCan() {
        return new TencentBeefCan();
    }
}

// 牛肉罐头抽象类
public abstract class BeefCan {
    /**
     * 闻起来如何
     * @return
     */
    public abstract String smell();
}

// 苹果罐头抽象类
public abstract class AppleCan {
    /**
     * 吃起来如何
     * @return
     */
    public abstract String taste();
}

// 罐头具体实现类
public class BaiduAppleCan extends AppleCan {
    
    @Override
    public String taste() {
        return "牛肉真香";
    }
}

public class TencentBeefCan extends BeefCan {
    
    @Override
    public String smell() {
        return "苹果真甜";
    }
}

在上面的 Demo 中:CanFactory、AppleCan 和 BeefCan 的关系已经确定, CanFactory 用来生产 AppleCan 和 BeefCan ,而 InternetCanFactory 作为 CanFactory 的实现类,可以生产 百度苹果罐头 和 腾讯牛肉罐头 。即抽象层定义工厂框架,实现层进行具体的实现。


2. 应用

在 Spring 中,BeanFactory 是用于管理 Bean 的一个工厂,所有工厂都是 BeanFactory 的子类。这样我们可以通过 IOC 容器来管理访问 Bean,根据不同的策略调用 getBean() 方法,从而获得具体对象。



个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 在项目A中,需要对多种文件类型资料进行解析,而解析后的结果是相同的,则可以通过抽象工厂模式为每种文件资料实现一个工厂类。如下:

    public class FileHandlerFactory {
        /**
         * pdf
         */
        public static final String FILE_PDF = "pdf";
        /**
         * excel
         */
        public static final String FILE_EXCEL = "excel";
    
        /**
         * 获取处理器
         * @param fileType
         * @return
         */
        public static FileHandler getFileHandler(String fileType) {
            if (FILE_PDF.equalsIgnoreCase(fileType)) {
                return new PdfFileHandler();
            } else if (FILE_EXCEL.equalsIgnoreCase(fileType)) {
                return new ExcelFileHandler();
            }
            return null;
        }
    
    }
    
    public abstract class FileHandler {
        /**
         * 处理
         */
       abstract void handle();
    }
    
    public class ExcelFileHandler extends FileHandler {
        @Override
        void handle() {
            System.out.println("ExcelFileHandler.handle");
        }
    }
    
    public class PdfFileHandler extends FileHandler{
        @Override
        void handle() {
            System.out.println("PdfFileHandler.handle");
        }
    }
    
    public class DemoMain {
        public static void main(String[] args) {
            FileHandler pdfHandler = FileHandlerFactory.getFileHandler(FileHandlerFactory.FILE_PDF);
            FileHandler excelHandler = FileHandlerFactory.getFileHandler(FileHandlerFactory.FILE_EXCEL);
    
            pdfHandler.handle();
            excelHandler.handle();
        }
    }
    

3. 总结

优点 : 抽象工厂模式隔离了具体类的生成, 使得客户并不需要知道什么被创建。 由于这种隔离,更换一个具体工厂就变得相对容易, 所有的具体工厂都实现了抽象工厂中定义的那些公共接口, 因此只需改变具体工厂的实例, 就可以在某种程度上改变整个软件系统的行为。当一个族中的多个对象被设计成一起工作时, 它能够保证客户端始终只使用同一个族中的对象。增加新的族很方便, 无须修改已有系统, 符合“开闭原则”。

缺点 : 增加新的等级结构麻烦, 需要对原有系统进行较大的修改, 甚至需要修改抽象层代码,这显然会带来较大的不便, 违背了“开闭原则”。

使用场景 : 一个系统不应当依赖于具体类实例如何被创建、 组合和表达的细节, 这对于所有类型的工厂模式都是很重要的, 用户无须关心对象的创建过程, 将对象的创建和使用解耦;系统中有多于一个的族, 而每次只使用其中某一族。 可以通过配置文件等方式来使得用户可以动态改变族, 也可以很方便地增加新的族。属于同一个族的对象将在一起使用, 这一约束必须在系统的设计中体现出来。 同一个族中的对象可以是没有任何关系的对象, 但是它们都具有一些共同的约束, 如同一操作系统下的按钮和文本框, 按钮与文本框之间没有直接关系, 但它们都是属于某一操作系统的, 此时具有一个共同的约束条件: 操作系统的类型。等级结构稳定, 设计完成之后, 不会向系统中增加新的等级结构或者删除已有的等级结构。


相关设计模式:

  • Builder 模式 : Abstract Factory 模式是通过调用抽象产品的接口来组装抽象产品,生成具有复杂结构的实例。Builder 模式 则是分阶段地制作复杂的实例。
  • Factory Method 模式:Abstract Factory 模式在零件和产品生成时可能会使用到该模式
  • Composite 模式:Abstract Factory 模式在制作产品时可能会使用该模式
  • Singleton 模 :Abstract Factory 模式的具体工厂时可能会使用该模式

扩展思路:

  • Abstract Factory 模式非常方便增加具体的工厂,即是修复具体工厂的Bug,都无需修改抽象工厂和调用部分。但是相对的,该模式难以增加新工厂方法,如果要增加新的工厂方法则需要将已经实现的具体工厂进行同步改造。

参考内容

抽象工厂模式在spring源码中的应用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/300046.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux_CentOS_7.9_Oracle11gr2配置数据库及监听服务自启动多方案实操之简易记录

前言: 作为运维保障,都无法准确预估硬件宕机的突发阶段,其生产数据实时在产出,那作为dba数据库服务以及相关Listener的其重要性、必要性就突显而出。这里拿虚拟机试验做个配置记录,便于大家学习参考。 实现方法一: 环境变量值::$ORACLE_HOME= /data/oracle/product/1…

ASP.NET Core基础之图片文件(二)-WebApi图片文件上传到文件夹

阅读本文你的收获&#xff1a; 了解WebApi项目保存上传图片的三种方式学习在WebApi项目中如何上传图片到指定文件夹中 在ASP.NET Core基础之图片文件(一)-WebApi访问静态图片文章中&#xff0c;学习了如何获取WebApi中的静态图片&#xff0c;本文继续分享如何上传图片。 那么…

基于机器视觉的车牌检测-边缘检测因子的选择

车牌检测概述 车牌识别在检测报警、汽车出入登记、交通违法违章以及移动电子警察方面应用广泛。车牌识别过程为&#xff1a;首先通过摄像头获取包含车牌的彩色图像&#xff1b;然后进行车牌边缘检测&#xff0c;先粗略定位到车牌位置&#xff0c;再精细定位&#xff1b;最后根…

你真的会用 Postman 吗?

1. 场景 提到 Postman&#xff0c;大部分的人可能只停留在使用 Postman「 模拟网络请求 」这单一功能上面。 事实上&#xff0c;Postman 的功能很强大&#xff0c;下面将罗列出几个比较少用&#xff0c;但实用的功能。 2. 抓包及拦截器 除了 Fiddler 和 Charles&#xff0c…

深入浅出Nacos的原理

前言 本文来讲一讲nacos作为底层注册中心的实现原理。那么就有这几个问题&#xff1f; 临时实例和永久实例是什么&#xff1f;有什么区别&#xff1f; 服务实例是如何注册到服务端的&#xff1f; 服务实例和服务端之间是如何保活的&#xff1f; 服务订阅是如何实现的&#…

idea怎么设置作者信息(详细)

目录 一&#xff1a;在Java类的开头自动注释作者名字和日期等信息 二&#xff1a;给Java的方法注释作者名字和日期等信息 1. 不可修改的模板&#xff1a;Postfix Completion 2. 可修改的模板&#xff1a;Live Templates tips&#xff1a;首先给大家推荐两款好用的免费软件&…

C语言汇总(持续更新)

大家好&#xff0c;这里是争做图书馆扫地僧的小白。非常感谢各位的支持&#xff0c;也期待着您的关注。 目前博主有着C语言、C、linux以及数据结构的专栏&#xff0c;内容正在逐步的更新。 希望对各位朋友有所帮助同时也期望可以得到各位的支持&#xff0c;有任何问题欢迎私信与…

IM即时通讯如何引领数字化时代的沟通革命?

在当今数字化时代&#xff0c;高效的即时通讯成为了企业无法缺少的工具。作为一款出色的IM即时通讯工具&#xff0c;WorkPlus以其卓越性能和创新功能&#xff0c;成为了最好的选择。 WorkPlus之所以被赞誉为优秀的IM即时通讯工具&#xff0c;首先在于其出色的性能。WorkPlus提供…

【强力推荐】GitCode AI开源搜索,面向开发者的专业AI搜索

一、GitCode AI开源搜索是什么&#xff1f; GitCode AI开源搜索 是面开发者的 AI 开源搜索工具&#xff0c;目的是为了帮助开发者快速寻找开源项目代码、解决开发问题和快速寻找答案&#xff0c;帮助开发者提升效率的同时利用代码仓托管能力建立自己个人知识库。 二、GitCode…

代码整洁之道:一个提升代码可读性的小技巧

写在开头 如何写好代码&#xff0c;一千个人眼中有一千个哈姆雷特&#xff0c;但是我认为有一点的是写好代码的公理&#xff0c;不可撼动&#xff0c;即对代码可读性的追求。最近在工作中面对了太多可读性不佳的代码&#xff0c;使得对于旧有代码的维护和分析困难重重&#xf…

Priors in Deep Image Restoration and Enhancement: A Survey

深度图像恢复和增强中的先验&#xff1a;综述 论文链接&#xff1a;https://arxiv.org/abs/2206.02070 项目链接&#xff1a;https://github.com/VLIS2022/Awesome-Image-Prior (Preprint. Under review) Abstract 图像恢复和增强是通过消除诸如噪声、模糊和分辨率退化等退化…

c语言的一些题(2024_1_7)

变种水仙花数 #include <stdio.h>int main() {int a 10000;for (; a < 100000; a){if ((a / 10000) * (a % 10000) (a / 1000) * (a % 1000) (a / 100) * (a % 100) (a / 10) * (a % 10) a)printf("%d ", a);}return 0; } //变种水仙花数 - Lily Num…

详细全面的postman接口测试实战教程

基本介绍 postman是一款流程的接口调试工具&#xff0c;其特点就是使用简单&#xff0c;功能强大。使用角色也非常广泛&#xff0c;后端开发&#xff0c;前端人员&#xff0c;测试人员都可以使用它进行接口调试或测试。 基本框架 如果把postman去其内容只保留框架的话&#…

vim/vi 模式切换和常用快捷键

vim/vi 切换模式&#xff1a; vim/vi 常用快捷键&#xff1a; 一般模式&#xff1a; gg&#xff1a;文件开头、G&#xff1a;文件结尾 shift^ &#xff1a;光标当前行首、shift^&#xff1a;光标当前行尾 yy&#xff1a;复制、p&#xff1a;粘贴、dd&#xff1a;删除当前行、…

JVM实战篇:内存调优

Java虚拟机进行生产环境线上问题解决以及性能问题的优化。 一.内存泄漏 内存泄漏&#xff08;memory leak&#xff09;&#xff1a;在Java中如果不再使用一个对象&#xff0c;但是该对象依然在GC ROOT的引用链上&#xff0c;这个对象就不会被垃圾回收器回收&#xff0c;这种情…

【Flutter 开发实战】Dart 基础篇:常见的数据类型

Dart 支持许多数据类型&#xff0c;包括我们常见的 Numbers&#xff08;数值类型&#xff09;、Strings&#xff08;字符串类型&#xff09;、Booleans&#xff08;布尔类型&#xff09;&#xff0c;也支持一些包括 Collections&#xff08;集合类型&#xff09;、Records&…

大数据毕业设计:新闻情感分析系统 舆情分析 NLP 机器学习 爬虫 朴素贝叶斯算法(附源码+论文)✅

毕业设计&#xff1a;2023-2024年计算机专业毕业设计选题汇总&#xff08;建议收藏&#xff09; 毕业设计&#xff1a;2023-2024年最新最全计算机专业毕设选题推荐汇总 &#x1f345;感兴趣的可以先收藏起来&#xff0c;点赞、关注不迷路&#xff0c;大家在毕设选题&#xff…

asp网站代码层面实现防cc攻击

CC主要是用来攻击页面的.大家都有这样的经历&#xff0c;就是在访问论坛时&#xff0c;如果这个论坛比较大&#xff0c;访问的人比较多&#xff0c;打开页面的速度会比较慢&#xff0c;对不?!一般来说&#xff0c;访问的人越多&#xff0c;论坛的页面越多&#xff0c;数据库就…

【攻防世界】Reverse——secret-galaxy-300 writeup

由main函数查看相关代码&#xff0c;但是代码中并没有直接的关于flag的信息&#xff1a; int __cdecl main(int argc, const char **argv, const char **envp) {__main();fill_starbase(&starbase);print_starbase((int)&starbase);return 0; } void __cdecl fill_sta…

【Docker基础三】Docker安装Redis

下载镜像 根据自己需要下载指定版本镜像&#xff0c;所有版本看这&#xff1a;Index of /releases/ (redis.io) 或 https://hub.docker.com/_/redis # 下载指定版本redis镜像 docker pull redis:7.2.0 # 查看镜像是否下载成功 docker images 创建挂载目录 # 宿主机上创建挂…