工厂设计模式
工厂模式的使用场景
-
工厂模式专门负责 **有大量共同接口的类(同一个抽象父类的多个子类)**的实例化工作。
-
解决了具体实例化new的问题,例如Road = new BitumenRoad(),用子对象沥青路作为当前使用的路,若道路类型经常改变,又想使用水泥路,林间小道等等,每次都要改所有地方,非常不便。
-
解决思路:封装变化点,将实例化的过程抽象,利用工厂隐藏创建细节,根据客户端的需要返回请求的实例化对象。
抽象工厂类的优点
- 隔绝了对象的创建和使用,解耦合
- 抽象了对象的创建,相比较简单工厂具体的创建方式,抽象工厂可以对一套接口可以有多套不同的实现方式
- 利于软件并行开发,每个具体实现人员都可以单元测试。
抽象工厂类的缺点
- 复杂,难以实现,需要总体架构师实现总体框架
- 工厂类用到反射,消耗性能,需要注意控制次数
简单工厂栗子
简单工厂负责将 对象的创建和对象的使用隔离开,实现一定的解耦合,并且可以在更换需求时做到尽量少的更改。
可能一个客户会使用到A产品,一开始都用的new A(),当再想要使用B产品时,就需要将所有new A() 改成new B(),这在较大的项目中是灾难性的,这就催生出了简单工厂,让客户无需显示的指明具体的产品,而是通过传参,配置文件的方式指明产品类型。一旦需求改变,利用反射甚至可以达到一行代码不改,只修改配置文件即可更换一套实现方式的效果。 这就是简单工厂的作用,上图即为简单工厂的栗子。
抽象工厂类的实现栗子
抽象工厂类图
工厂类的一般分工结构
SYS system文件夹
由总架构师负责 不允许修改 负责总体DaoFactory的构建 和各个类的接口,负责指定规范。
DTO 文件夹
由总架构师负责 不允许修改 负责数据实体的构建
Client Server文件夹
由其他人实现,需要实现总架构师的接口规范,实现自己负责的系列的所有Dao对象应该实现的具体的操作
个人的理解: 总架构师负责搭建起整体的抽象框架定义好逻辑所需的接口和方法,其只负责定义规范,需要做什么(IUserDao,ICharacterDao),而不关心该怎么做。
简单工厂 到 抽象工厂
例如User信息需要进行增删查改,一开始可能只要在客户端下进行,通过简单工厂类,将IUserDao接口的实现逻辑,用简单工厂实现,对客户端提供静态方法获取,此时就可以解决后续如果实现对象改变无需全局的更换类名,只需更改简单工厂即可。但如果后续Leader让在服务端也实现一套User的增删改查,和客户端的实现逻辑完全不同,可以考虑再为服务端创建一个简单工厂,但如果再有更多端,创建非常多的静态简单工厂,无疑是灾难性的,因此基于这种情况,衍生出了抽象工厂,用户在调用 抽象工厂时实际上会根据需求选择一个子类的具体工厂(服务端,客户端…),具体的子类工厂再生产其对应的对象,供客户端进行调用,隔离了对象的使用和创建,大大的解耦合了。
分工依据
大师设计分析:Program相等于脚本用来调用核心代码,通过调用关系不难发现 脚本调用的引用类型全是大师设计的接口 无需关心底层具体实现即可调用
相关人员只需要负责自己系列应该如何实现这些接口的方法规范即可使整体趋于完整,有助于软件的并行开发(脚本和数据访问层同时开工)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dLvTNTYc-1647850167375)(工厂模式.assets/分工依据2.png)]
相关人员设计分析:粉色箭头指向的是相关人员根据大师设计的接口实现的一些类,其都需要实现大师设计类的接口或继承抽象类以适应规范。 例如此案例中粉红1对应橘黄1 负责服务端Server开发的人员 设计的ServerDaoFactory继承了大师的抽象类DaoFactory,并把其对象赋值给大师的实例Instance;同理橘黄2需要一个IUserDao的具体实现,粉红2即为IUserDao 引用 真正实现赋值的具体类对象是分红3对应的UserServerDao,最终调用的方法也是其中实现的具体方法。
具体代码实现
抽象工厂类DaoFactory.cs
namespace DAO
{
public abstract class DaoFactory
{
//关键点:返回一个引用为自己的示例对象 赋值的对象为自己的子类
//子类选择根据数据访问形式(系列,服务端还是客户端)决定
//私有字段 防止反射多次进行
private static DaoFactory instance;
public static DaoFactory Instance
{
get
{
//这里的用法不符合开闭原则 增加新系列仍需修改代码 后续用反射实现优化
/*if (Config.DataType == "Client") return new ClientDaoFactory();
else if (Config.DataType == "Server") return new ServerDaoFactory();
else return null;*/
//修改 利用反射,反射可以动态创建对象,软件并行开发,大师在设计此类时无法使用子类的ClientDaoFactory等
//所以无论是考虑开发过程 还是开闭原则无需修改代码都应使用反射动态创建返回一个对象
//反射可以通过“字符串名”获取对象,所以可以针对子类的名的字符串指定规范
//子类设计:必须在规定命名空间下,类名xxxDaoFactory xxx必须是系列名 Config.DataType
if (instance == null)
{
//反射消耗性能所以只用它第一次当赋值后就不要使其反射
Type type = Type.GetType("DAO." + Config.DataType + "DaoFactory");
instance = Activator.CreateInstance(type) as DaoFactory;
}
return instance;
}
}
public abstract IUserDao UserDao {
get; }
public abstract ICharacterDao CharacterDao {
get; }
}
}
接口ICharacterDao.cs
namespace DAO
{
public interface ICharacterDao
{
public void Add(Character character);
}
}
接口IUserDao.cs
namespace DAO
{
public interface IUserDao
{
public void Add(User user);
}
}
数据对象类 User.cs Character.cs 这里简化不在进行封装,里面应该封装符合类职责的属性和方法
以上均为总架构师(大师)应该完成的工作,设计完成后不应随意改变,下面为相关人员的简易代码
服务端实现
服务端工厂类 ServerDaoFactory.cs
namespace DAO
{
public class ServerDaoFactory : DaoFactory
{
public override IUserDao UserDao
{
get
{
return new UserServerDao();
}
}
public override ICharacterDao CharacterDao
{
get
{
return new CharacterServerDao();
}
}
}
}
服务端客户dao实现类 UserServerDao.cs
namespace DAO
{
public class UserServerDao : IUserDao
{
public void Add(User user)
{
Console.WriteLine("调用客户服务端数据访问对象");
}
}
}
服务端角色dao实现类 CharacterServerDao.cs
namespace DAO
{
public class CharacterServerDao : ICharacterDao
{
public void Add(Character character)
{
Console.WriteLine("调用角色服务端数据访问对象");
}
}
}
客户端实现
客户端工厂类 ClientDaoFactory.cs
namespace DAO
{
public class ClientDaoFactory : DaoFactory
{
public override IUserDao UserDao
{
get
{
return new UserClientDao();
}
}
public override ICharacterDao CharacterDao
{
get
{
return new CharacterClientDao();
}
}
}
}
客户端客户dao类实现 UserClientDao.cs
namespace DAO
{
public class UserClientDao : IUserDao
{
public void Add(User user)
{
Console.WriteLine("调用客户S客户端数据访问对象");
}
}
}
客户端角色dao类实现 CharacterClientDao.cs
namespace DAO
{
public class CharacterClientDao : ICharacterDao
{
public void Add(Character character)
{
Console.WriteLine("调用角色客户端数据访问对象");
}
}
}