首页 > 其他分享 >设计模式:从接口的角度切入静态工厂模式

设计模式:从接口的角度切入静态工厂模式

时间:2024-06-11 23:33:52浏览次数:23  
标签:String 实现 接口 工厂 切入 设计模式 public 客户端

面向接口编程的意义

所谓面向接口去编程的核心含义就是为了——“封装隔离

通常的封装,是指对数据结构的封装,将几种数据类型整到一块,组成一个新的数据类型;而java中的封装,包含对数据和行为进行抽象,然后定义出一个类,这个类即封装了数据和行为,但接口这里的封装,更多的是指对于行为(能力、方法)的封装,是一种“对被隔离体能力的封装”,而隔离对应的就是,外部的调用以及内部的实现,外部只根据接口来调用方法(根据菜单来点菜,具体填饱肚子的菜是内部去做),外部调用是不知道内部你是用什么方式实现的,举个例子,就像我有一个计算器,计算器的加减乘除按键就是我提供给用户的接口,用户只知道我有加减乘除的能力,但当他用乘法按键去运算的时候,后台具体是用二进制运算,还是逐个数累加或者其他什么方式来完成这个乘法功能,用户是不知道的。也就是外部调用和内部实现是被隔离开的。

既然外部调用和内部实现被隔离开了,那么只要接口不变,内部实现怎么变化都不会影响外部应用对这个接口的调用,从而让系统更加的灵活,更便于扩展和维护,也就是传说中的“接口是系统可插拔的保证”。

总之,在开发中,优先选择使用接口,接口的意义对实现上是定义指导,在使用上是说明。

从架构设计的角度来看接口

在java的设计中,经常出现的层的概念和模块的概念,以Java Web最经典的MVC架构模式为例,MVC是典型的分层架构,即控制层、逻辑层、数据层,而层与层之间的“沟通”,用的就是接口:

在每一层里,又包含很多模块,每个模块对外则是一个整体,所以一个模块应该对外提供接口,其他地方需要某个功能时,可根据接口直接调用模块,也就是上面的 “ 接口是被其隔离部分的外观”。

简单工厂

先看一个例子,用java实现两个字符串相似的程度:

先定义接口,声明功能

public interface MatcherAlg {

    /**
     * 计算两个串的相似度
     * @param srcStr
     * @param dstStr
     * @return Float 相似值
     */
   public Float CalculateSimilarityRatioValue(String srcStr,String dstStr);

}

内部实现一下  

public class JaccardMatcher implements MatcherAlg {

    @Override
    public Float CalculateSimilarityRatioValue(String srcStr, String dstStr) {
        if(srcStr == null && dstStr ==null){
            return 1f;
        }
        if(srcStr == null || dstStr == null){
            return 0f;
        }
        Set<Integer> aChar = srcStr.chars().boxed().collect(Collectors.toSet());
        Set<Integer> bChar = dstStr.chars().boxed().collect(Collectors.toSet());

        int intersection = SetUtils.intersection(aChar,bChar).size();
        if(intersection == 0){
            return 0f;
        }
        int union =  SetUtils.union(aChar,bChar).size();
        return ((float)intersection/(float)union);
    }
}
public class Test{
  
   public static void main(String args[]){
        String str= "sdfsf";
        String dst= "1234d";
        MatcherAlg matcher = new JaccardMatcher();
        Float result = matcher.CalculateSimilarityRatioValue(str,dst);
    }

}

仔细看下来,这样我定义那个 MatcherAlg 接口,后面又 

  MatcherAlg matcher = new JaccardMatcher();

好像是在 “脱了裤子放p”,没事找事。干嘛不直接定义JaccardMatcher类,然后:

JaccardMatcher matcher = new JaccardMatcher();

但是上面说过了,我们应该面向接口编程,接口的核心就为了 “封装隔离”,实现类JaccardMatcher应该是被接口 MatcherAlg封装并同客户端隔离开来。

客户端根本不应该知道 JaccardMatcher的存在,更不用说 new JaccardMatcher()这种“脱裤放p”操作了。但是问题又来了,如果客户端没有new JaccardMatcher(),只有MatcherAlg接口的定义,那么后面的代码是无法使用的。

于是纠结的地方出现了,上面花了那么大篇幅说怎么怎么面向接口,纯面向接口了你又不能运行了,能运行又违反了“隔离封装”了, 问题进入死环了。

 所以“脱裤放p”的操作是对应这个死环一种蹩脚的写法(它可以运行,但专业的我们不认)。

这个死环如何解决,我们先看一下设计模式中的一段话,它是这样说的 :提供一个创建对象实例的功能,而无需关系其具体的实现。被创建实例的类型可以是接口、抽象类、也可以是具体的类。

受到那句话的启发,我们尝试得出一个解开上面那个死环的方案:我们在模块内部建一个类,这个类的功能就是创建可使用的接口,并且把创建的接口提供给客户端,这一客户只需要根据这个类来获取相应的接口对象,于此同时,接口具体使用哪个实现,我们就可以抽离到这个类里面,给我们提供了一个控制 使用哪个类的 隔离扩展区,客户端也不需要关心他用的这个类是对应哪种实现,如何实现的。

上面这套思想,设计模式中称之为 “工厂”

简单工厂的模式结构

//客户端类
public class Client {
    public static void main(String[] args) {
        Product p = SimpleFactory.makeProduct(Const.PRODUCT_A);
        p.show();
    }
}
//抽象产品
    public interface Product {
        void show();
    }
    //具体产品:ProductA
    public class ConcreteProduct1 implements Product {
        public void show() {
            System.out.println("具体产品1显示...");
        }
    }
    //具体产品:ProductB
    public class ConcreteProduct2 implements Product {
        public void show() {
            System.out.println("具体产品2显示...");
        }
    }
//枚举
public final class Const {
        static final int PRODUCT_A = 0;
        static final int PRODUCT_B = 1;
        static final int PRODUCT_C = 2;
 }
//工厂
public class SimpleFactory {
        public static Product makeProduct(int kind) {
            switch (kind) {
                case Const.PRODUCT_A:
                    return new ConcreteProduct1();
                case Const.PRODUCT_B:
                    return new ConcreteProduct2();
            }
            return null;
        }
    }

简单工厂的理解

首先看上面简单工厂的样例代码,会让人困惑,不就是把new操作从客户端移动到了额外的类里去了么,本质还是new 了一个实现类,这里我们再次回到原点,我们前面提到的接口,接口是用来封装隔离的,目的就是让客户端不要知道封装体内的具体实现,简单工厂的位置是处于封装体内的,简单工厂跟接口的具体实现在一起,算是封装体内部的一个类,所以简单工厂知道具体的实现类是没有关系的,我们再来看一下简单工厂的类图:

图中浅蓝色的虚线框即为一个封装的边界,表示接口、工厂、实现类组合成了一个组件,在这个组件中,只有接口和工厂是对外的,也只有这俩,外界可以使用和访问到,但是具体的实现类,完全是内部的,对外透明的,不可见的,所以它被全包裹进蓝框,对于客户端而言,它只知道这个Alg接口和生产含有Alg功能实例的工厂,通过Factory就能获取Alg的能力了,所以,new操作划在工厂内,在设计和隔离的意义上,有了质的变化。

静态工厂

简单工厂又叫做静态工厂,所谓静态工厂,就是我们使用工厂的时候,不需要实例化工厂了,直接将生产的方法设为静态方法,通过类名即可调用,或者做成单例的模式,也就是说简单工厂的方法通常都是静态的,所以称之为静态工厂;一个简单工厂可以包含很多用来构建东西的方法,这些方法可以创建不同的接口、实力类,一个简单的工厂理论上可以构造任何东西,所以又称之为“万能工厂”

简单工厂的本质

简单工厂的本质是:选择实现

选择实现,重点在于选择,实现是已经做好了的,就算实现再简单(哪怕是new实例)也要由具体的实现类来实现,而不是在简单工厂里面来实现,简单工厂的目的在为客户端提供一个选择,选择哪种实现,从而使客户端和具体的实现之间解耦。这样具体实现无论如何变动,都不需要客户端随之变动,这个变动会在工厂这一层里被吸收和隔断。

实现简单工厂的难点在于“选择”的实现,可以通过传参,也可以通过动态的参数,比如在运行期间去读取配置文件或数据库、内存中的某个值,根据这个值来进行具体的实现。

简单工厂的扩展:配置型工厂

工厂模式已经有较为明确的模板了,现在有一个问题,就是如果MatcherAlg的实现类不止一个,我们可以通过在工厂的方法中传入参数来处理

public Class Factory{

  public static MatcherAlg createAlg(String type){

        if( type.equals("a") ){
            return new aAlg();
        }else if ( type.equals("b") ){
            return new bAlg();
        }else{
               ……
        }

}
}

可是,当需要“又又又”扩展了新的实现类的时候,if else 又需要扩展一句,同时对客户端也要告知,这样对于Factory这个类来说,严重违反了开闭原则。

为了解决这个问题,我们可以通过配置文件的形式来解决,当有了新的实现类或者需要默认指定用哪一个实现的时候,只需要通过配置文件的配置项即可,通过配置文件的方式,多需要使用java的反射来支持动态建立对象。这里摘取自己的一个代码来作为一个样例:

/**
 * 基础工厂,其他组件工厂的实现可用基于该类进行扩展
 * 功能:根据配置文件动态生成对象
 * @author GCC
 */
public abstract class AbstractFactory {

    private static Logger logger = Logger.getLogger(AbstractFactory.class);

    //默认自带的类控制配置文件
    private final static String DEFAULTCONFIG_FILE_URL = "factoryconfig.ini";

    //默认的配置文件
    static URL defaultConfigFileUrl = AbstractFactory.class.getClassLoader().getResource(DEFAULTCONFIG_FILE_URL);

    /**
     * 根据配置文件以及key值,获取对象的类路径
     * @param url   配置文件路径
     * @param key   关键字
     * @return  String 类路径
     */
    static String getClassUrl(String url,String key){
        ConfigUtil config = new ConfigUtil(url);
        return config.getValueByConfigkey(key);
    }

    /**
     * 根据指定配置文件及指定关键字生成对象
     * @param url   配置文件路径
     * @param key   关键字
     * @return  Object 具体对象
     */
    static Object getObject(String url,String key){
        String classurl = getClassUrl(url,key);
        try{
            Class oneclass = Class.forName(classurl);
            return oneclass.newInstance();
        }catch (Exception e){
            logger.error(e.getMessage() +" plase check"+ DEFAULTCONFIG_FILE_URL );
        }
        return null;
    }



}

配置文件(.ini文件)内容:

#matcher.algclassurl:算法类地址
matcher.algclassurl=org.gds.matcher.impl.LevenshteinMacther

简单工厂的不足

简单工厂实现简单,非常友好的提供了一套实现组件封装的功能,同时也解决了客户端何内部实现类的强耦合,实现了解耦。这是简单工厂的优点,但世事都是两面的,它也有不可避免地缺点:

首先,它增加了客户端的复杂程度,如果通过客户端的参数来选择具体的实现类,那客户端必须额外需要一份枚举表或者字典,并且知道每个枚举的意义,这样会增加客户端的复杂程度,同时一定程度上暴露了内部的实现(虽然可配置方案一定程度上可以对冲这一问题)。

其次,简单工厂使用静态方法(又叫静态工厂)来创建接口,当面临一些复杂的组件创建,静态方法会非常庞大,无法通过继承来扩展创建接口的方法的行为了。

标签:String,实现,接口,工厂,切入,设计模式,public,客户端
From: https://blog.csdn.net/qq_40690073/article/details/139562214

相关文章

  • 设计模式--1.0.2
    工厂模式Version1.0.2工厂模式提供一种创建对象的方式,而无需指定要创建的具体类。通过使用工厂模式,可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象,这样可以提高代码的可维护性和扩展性。意图定义一个创建对象的接口,让其子类决定实例化哪一个具......
  • 利用聚合API平台的API接口,利用HTTP协议向服务器发送请求,并接受服务器的响应,要求利用cJ
    目录题目分析代码结果题目利用聚合API平台的API接口,利用HTTP协议向服务器发送请求,并接受服务器的响应,要求利用cJSON库对服务器的响应数据进行解析,并输出到终端分析1.需从源代码网站GitHub或SourceForge代码网站下载cJSON库及阅读下载的README相关手册如何使用cJSON库;2.使用......
  • 换个色调换个心情,龙迅解决方案,机箱副屏接口全兼容
    1:LT8619CHDMI1.4/DualModeDPReceiver(TTL/LVDS)2:LT6911/LT6911C/HDMI1.4Receiver(4/2-PortMIPI/LVDS)3:LT7911DType-C/DPto4/2-PortMIPI/LVDS以下产品图转载自:乔思伯D31机箱MESH副屏版机箱实测–原创分享(新)–Chiphell–分享与交流用户体......
  • 常见设计模式
    设计模式Version1.01.设计模式的类型创建型模式提供一种在创建对象的同时隐藏创建逻辑的方式。使得程序在判断针对某个实例需要创建哪些对象时更加灵活工厂模式,单例模式,原型模式结构型模式关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构......
  • 振动电阻式传感器测量模块的传感器接口
    振动电阻式传感器测量模块的传感器接口RM502模块采用了高精度模拟信号驱动和采集技术,能够驱动和测量对电阻精度要求较高的传感器。它采用恒流驱动和实时电流测量,有效避免了环境温度变化引起的测量误差。同时,它具有高精度差分AD转换和可编程增益放大功能,能够对小信号具有极高的灵......
  • DBAPI 数据接口发布平台功能介绍
    书接上文:一、登录  admin/admin 二、功能介绍1.数据源 这里用mysql数据源展示效果 2.API(4.0.10版本这里有个BUG,就是默认不显示API,点搜索才能出来,希望官网后续可以改进~)API这里给个三个配置模块,基本信息和执行器是必须要填写的。这里的访问权限有私有API和开放......
  • DBAPI 安装部署standalone 数据API接口发布平台
    DBAPI项目地址:https://gitee.com/freakchicken/db-api项目简介:零代码开发api服务,只需编写sql,就可以生成httpapi服务。支持api动态创建,兼容多种数据库。适用于BI报表、数据可视化大屏的后端接口快速开发。旨在为企业数据服务的发布提供完整解决方案一、下载安装包 在浏览......
  • 第十章:接口
    一、接口    1.接口:是一种标准,接口的实现者和使用者必须遵循的约定    2.接口的语法:        (1)接口的关键字:interface        (2)语法:                interface接口名{}    ......
  • c/c++ 设计模式-----职责链(Chain Of Responsibility)模式
    一个关于涨薪审批的范例#include<iostream>#ifdef_DEBUG//只在Debug(调试)模式下#ifndefDEBUG_NEW#defineDEBUG_NEWnew(_NORMAL_BLOCK,__FILE__,__LINE__)//重新定义new运算符#definenewDEBUG_NEW#endif#endif//#include<boost/type_index.hpp>usingnames......
  • C#、.Net通过HttpWebRequest请求WebService接口
    点击查看代码///<summary>///测试按钮中调用WebService接口///</summary>///<paramname="sender"></param>///<paramname="e"></param>privatevoidbutton1_Click(objectsender,EventArgse){//strin......