SAF 中抽象工厂的实现

2024-08-29 23:48
文章标签 实现 抽象 工厂 saf

本文主要是介绍SAF 中抽象工厂的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文是《Developing Application Frameworks in .NET》的读书笔记。SAF 是书中的一个范例框架,意为 Simple Application Framework(简单应用程序框架)。这篇文章主要向大家说明了SAF中抽象工厂模式的实现方式。

设计思想概述

抽象工厂是很常用的一种创建型模式,它的主要作用在于向程序员屏蔽了创建对象的复杂细节,在获取对象时,只需要在工厂类上调用 GetXXX(),或者 CreateXXX(),便可以得到一个类型实例,而不是通常所使用的new XXX()这种方式。关于抽象工厂更详细的内容可以参考 奇幻RPG(人物构建与Abstract Factory模式) 这篇文章。SAF 的抽象工厂也是基于GOF的这个基本模型,但是做了下面两个改进:

利用反射,根据字符串动态创建工厂

我们获得工厂类时,通常还是通过AbstractFactory factory = new ConcreteFactory()这种方式获得。这样如果我们想更换一个工厂实体类时,比如说,将上面的ConcreteFactory换成 AnotherFactory,我们需要修改代码为 AbstractFactory factory = new AnotherFactory(),这样免不了需要再次重新进行编译。

在SAF中,使用了自定义Config配置节点,将 工厂名称  它的类型信息(所在的程序集、命名空间、类型名称)全部保存在了App.Config文件中,并创建相应的ClassFactoryConfiguration 类型用于获取App.Config中的节点信息。然后创建ClassFactory类型,并在其中添加静态方法GetFactory(string name),其中name代表了工厂名称(必须与App.Config中保存的名称匹配)。然后,在客户端调用这个方法来创建工厂。通过这种方式,我们只需要在Config中保存所有的工厂类信息,然后就可以为GetFactory()通过传入不同的参数,创建不同的工厂类。

实际的流程如下:

  1. 客户端程序调用  ClassFactory.GetFactory(string factoryName) 方法,传入工厂名称。

  2. 在GetFactory() 方法中一个ClassFactoryConfiguration对象,这个对象封装了获取app.Config中信息的方法。

  3. ClassFactoryConfiguration 根据 factoryName参数在app.Config中寻找与工厂名称匹配的节点,找到后,返回该工厂的类型信息。

  4. GetFactory()方法根据类型信息,使用反射创建类型实例,然后返回该实例。

使用Remoting创建远程工厂

SAF中的另一个改进是引入了Remoting,关于Remoting的详细内容,可以参考 .Net中的Remoting 这篇文章。有的时候,我们需要使用Remoting获取位于远程服务器中的对象,这时往往需要写一些注册Channel、转换对象类型的代码。SAF的抽象工厂将这些细节也屏蔽了起来,只要将获取远程对象的地址写在配置文件中,比如http://localhost:8989/ClassFactory,剩下的步骤与使用本地对象别无二致,极大的简化了操作。

接下来我们看一下代码实现。

代码实现

抽象工厂类的实现

首先是创建抽象工厂类的体系,新建一个解决方案ClassFactory,再其下建立一个类库项目 Factory,它包含了下面一些类,以构成一个基本的抽象工厂模式,作为范例:

namespace Factory { 
    /* 工厂基类
     * **************************************************/
    public abstract class ProductFactory : MarshalByRefObject{
        public abstract Product GetCheapProduct();
        public abstract Product GetExpensiveProduct();
    }

    // 工厂实体类 (一般产品)
    public class ConcreteProductFactory : ProductFactory {
        public override Product GetCheapProduct() {
            return new CheapProduct();
        }
        public override Product GetExpensiveProduct() {
            return new ExpensiveProduct();
        }
    }

    // 工厂实体类 (新产品)
    public class ConcreteNewProductFactory : ProductFactory {
        public override Product GetCheapProduct() {
            return new NewCheapProduct();
        }
        public override Product GetExpensiveProduct() {
            return new NewExpensiveProduct();
        }
    }

    // 工厂实体类 (远程产品) 使用Remoting
    public class ConcreteRemoteProductFactory : ProductFactory {
        public override Product GetCheapProduct() {
            return new RemoteCheapProduct();
        }
        public override Product GetExpensiveProduct() {
            return new RemoteExpensiveProduct();
        }
    }

    /* 产品基类 MashalByRefObject
     * **************************************************/
    public abstract class Product : MarshalByRefObject {
        public abstract string Name { get; }
        public abstract int GetPrice();
        public abstract string GetColor();
    }

    // 一般产品实体类 ( 由 ConcreteProductFactory 创建 )
    public class CheapProduct : Product { 
        private const int cost = 10;
        private const string color = "red";
        private const string name = "Cheap Product";

        public override int GetPrice() {
            return cost * 2;
        }
        public override string GetColor() {
            return color;
        }
        public override string Name {
            get {
                return name;
            }
        }
 }

    // 一般产品实体类 ( 由 ConcreteProductFactor 创建 )
    public class ExpensiveProduct : Product { /* 实现略 */ }

    // 新产品实体类 ( 由 ConcreteNewProductFactory 创建 )
    public class NewCheapProduct : Product { /* 实现略 */ }

    // 新产品实体类 ( 由 ConcreteNewProductFactory 创建 )
    public class NewExpensiveProduct : Product { /* 实现略 */ }
    
    // 远程产品实体类 ( 由 ConcreteRemoteProductFactory 创建 )
    public class RemoteCheapProduct : Product { /* 实现略 */ }

    // 远程产品实体类 ( 由 ConcreteRemoteProductFactory 创建 )
    public class RemoteExpensiveProduct : Product { /* 实现略 */ }
}

这里需要注意的是 ProductFactory和 Product 抽象类均继承自 MarshalByRefObject 基类,这是因为这两个类的子类实现有一部分位于客户端、还有一部分位于服务器端,为了能从服务器端向客户端进行封送(Marshal),它们必须声明为继承自MarshalByRefObject。

我们注意到上面这段程序的两个 远程产品实体类(RemoteCheapProduct和RemoteExpensiveProduct) 应该是仅存在于服务器端的,等下我们会细说这里。现在我们编译上面的代码,会在Bin目录下生成Factory.dll文件。

服务器端的实现

服务器端的实现很简单,仅仅是开启Remoting服务,供客户端进行调用就可以了。在上面已经说明,服务器端仅需要提供两个远程产品实体类(RemoteCheapProduct 和 RemoteExpensiveProduct),所以只需要提供一个 ConcreteRemoteProductFactory 工厂类型就可以了。现在我们再创建一个解决方案,名为FactoryServer,在其下添加一个Console控制台项目RemoteServer,接下来,我们 把上一步生成的Factory.dll 文件复制到Bin目录下,然后引用它(不要引用项目)。

这里需要注意:因为Factory包含了远程所要提供的对象,所以我们需要引用它;而它也包含了客户端可以直接创建的对象,所以客户端也需要引用Factory.dll,但是我们不能将Factory服务器端所提供的内容交给客户端(如果可以的话,我们根本用不着Remoting),所以我们在客户端引用Factory之前要屏蔽掉服务器端实现的内容。如果直接引用项目(服务器端、客户端同时引用了Factory项目),一是没有起到服务端、客户端分离的作用,二是一旦我们屏蔽掉Factory项目中的内容,那么重新生成之后,服务器端也丧失了这些内容(所以我们在屏蔽部分内容之前将生成好的dll复制到FactoryServer的Bin目录下,然后引用之)。

服务器端通过配置的方式开启Remoting服务,app.Config中的配置如下:

<system.runtime.remoting>
    <application>
        <service>
            <wellknown 
                mode="Singleton"
                  type="Factory.ConcreteRemoteProductFactory, Factory"
                  objectUri="ClassFactory" />
        </service>
        <channels>
            <channel ref="http" port="8989"/>
        </channels>
    </application>
</system.runtime.remoting>

接着,编写Program代码,仅仅是根据配置开启服务而已:

public class Program {
    static void Main(string[] args) {
        // 根据 App.config.exe 中的配置开启远程服务
        RemotingConfiguration.Configure("RemoteServer.exe.config", false);

        Console.WriteLine("远程服务成功开启!");
        Console.ReadLine();
    }
}

客户端的实现

我们在上面 抽象工厂类 的实现中,已经创建了一个解决方案ClassFactory,以及其下的Factory项目。我们将ClassFactory解决方案整体视为客户端,此时,客户端不应包含服务端的代码实现(RemoteCheapProduct类 和 RemoteExpensiveProduct类),所以我们将它删除或者注释掉。另外,由于Config中保存的类型信息是工厂类型的信息,比如ConcreteRemoteProductFactory类型的信息,所以当我们通过反射创建远程工厂类型的实例时,客户端依然需要ConcreteRemoteProductFactory的类型信息,所以我们不能屏蔽掉ConcreteRemoteProductFactory,但是我们可以让它什么都不做(Empty Class),现在修改ConcreteRemoteProductFactory 如下所示:

// 工厂实体类 (远程产品) 使用Remoting
public class ConcreteRemoteProductFactory : ProductFactory {
    public override Product GetCheapProduct() {
        throw new Exception("由服务器端实现");
    }
    public override Product GetExpensiveProduct() {
        throw new Exception("由服务器端实现");
    }
}

这个类仅仅是为了在客户端提供一个类型信息,以便能够通过反射创建类型,实际上只要访问的是服务器端,那么这里永远都不会抛出异常,因为这个类实际是在服务器端创建的。

现在修改app.config文件,记录我们所有的工厂类的 名称 及类型信息。名称可以任意起,在创建类型的时候调用的GetFactory(string name)方法的name参数要与这里匹配;类型信息必须为客户端拥有的工厂类类型,用于通过反射创建对象:

<configSections>
    <section name="classFactory" type="ConfigManager.ClassFactorySectionHandler, ConfigManager" />
</configSections>

<classFactory>
    <factory name="ProductFactory-A" type="Factory.ConcreteProductFactory, Factory" />
    <factory name="ProductFactory-B" type="Factory.ConcreteNewProductFactory, Factory" />
    <factory name="Remote-ProductFactory-C" location="http://localhost:8989/ClassFactory" type="Factory.ConcreteRemoteProductFactory, Factory" />
</classFactory>

注意到name属性为" Remote-ProductFactory-C "的节点,它拥有一个location属性,这个属性保存了获取它的远程地址,用于Remoting访问。

接下来,我们要创建对这个节点的处理程序,ClassFactorySectionHandler。在ClassFactory解决方案下再添加一个类库项目:ConfigManager,然后添加ClassFactorySectionHandler类,它用于处理节点(如不清楚,可以参考 .Net自定义应用程序配置):

// 结点处理程序,在调用 ConfigurationManager.GetSection("sectionName") 自动调用这里
public class ClassFactorySectionHandler : IConfigurationSectionHandler {
    public object Create(object parent, object configContext, XmlNode section) {
        return new ClassFactoryConfiguration(section);
    }
}

// 结点配置程序
public class ClassFactoryConfiguration {
    private XmlNode node;

    // 这里的 node 为 classFactory 结点
    public ClassFactoryConfiguration(XmlNode section) {
        this.node = section;
    }

    // 获取与name属性匹配的子结点的 type 属性
    public string GetFactoryType(string name) {

        // 获取子结点,使用 XPath 语法查找
        XmlNode childNode = node.SelectSingleNode("factory[@name='" + name + "']");

        if (childNode != null && childNode.Attributes["type"] != null)
            return childNode.Attributes["type"].Value;
        else
            return null;
    }

    // 获取与name属性匹配的子结点的 location 属性 
    // location 属性指定远程对象的获取位置
    public string GetFactoryLocation(string name) {
        // 获取子结点,使用 XPath 语法查找
        XmlNode childNode = node.SelectSingleNode("factory[@name='" + name + "']");

        if (childNode != null && childNode.Attributes["location"] != null)
            return childNode.Attributes["location"].Value;
        else
            return null;
    }
}

我们只需要注意两个方法就可以了:GetFacotryType(string name),获取工厂类的类型,用于通过反射创建类型;GetFactoryLocation(string name),获取工厂类的地址,用于Remoting。

接下来我们在解决方案下再创建一个控制台项目,Main,它是我们的客户程序。在其下创建ClassFactory类,我们的所有客户端代码实际上通过这个类的静态方法GetFactory(string name)获取工厂类:

public class ClassFactory {
    // 不允许使用 new 创建
    private ClassFactory() {}
    
    // 根据结点的name属性创建Factory对象
    public static object GetFactory(string name) {
        object factory = null;

        // 获取配置对象
        ClassFactoryConfiguration config = 
    (ClassFactoryConfiguration)ConfigurationManager.GetSection("classFactory");

        // 获取Factory位置,用于Remoting远程对象
        string location = config.GetFactoryLocation(name);

        // 获取Factory类型
        string type = config.GetFactoryType(name);

        if(type == null) return null;

        Type t = Type.GetType(type);

        try {
            if (location != null) {
                factory = Activator.GetObject(t, location);    // 创建远程对象
            } else {
                factory = Activator.CreateInstance(t);         // 创建本地对象
            }
        }
        catch {
            throw new Exception("使用反射创建对象失败!");
        }

        return factory;
    }
}

代码测试

现在我们对代码进行一下测试:

public class Program {
    static void Main(string[] args) {

        // 创建根据配置创建工厂类
        ProductFactory pfA = (ProductFactory)ClassFactory.GetFactory("ProductFactory-A");
        ProductFactory pfB = (ProductFactory)ClassFactory.GetFactory("ProductFactory-B");
        ProductFactory pfC = (ProductFactory)ClassFactory.GetFactory("Remote-ProductFactory-C");

        // 创建本地产品
        Product p1 = pfA.GetCheapProduct();
        Product p2 = pfA.GetExpensiveProduct();
        Product p3 = pfB.GetCheapProduct();
        Product p4 = pfB.GetExpensiveProduct();

        // 创建远程产品
        Product p5 = pfC.GetCheapProduct();
        Product p6 = pfC.GetExpensiveProduct();

        // 打印产品
        PrintDescription(p1);
        PrintDescription(p2);
        PrintDescription(p3);
        PrintDescription(p4);

        // 打印远程产品
        PrintDescription(p5);
        PrintDescription(p6);

        Console.WriteLine("Press enter to finish");
        Console.ReadLine();
    }

    private static void PrintDescription(Product p) {
        Console.WriteLine("{0}", p.Name);
        Console.WriteLine("---------------------------");
        Console.WriteLine("Color: {0}", p.GetColor());
        Console.WriteLine("Price: {0} \n", p.GetPrice());
    }
}

在运行之前,先运行服务端,然后再运行客户端,可以得到下面的输出结果:

Cheap Product
---------------------------
Color: red
Price: 20

Expensive Product
---------------------------
Color: red
Price: 100
// 略 ...
Press enter to finish

感谢阅读,希望这篇文章可以为你带来帮助!


这篇关于SAF 中抽象工厂的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1119200

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机