解耦的架构

本文关键字:解耦 | 更新日期: 2023-09-27 18:08:44

我正在开发一个系统,在这个系统中,我想让我的层尽可能解耦,你知道,某种模块化应用程序,能够在不严重修改系统其他部分的情况下切换数据库和其他东西。

所以,我一直在看Robert C.Martin关于良好实践、干净代码、解耦架构等的演讲,以获得一些灵感。我觉得有点奇怪的是他对系统Fitnesse的描述,以及他们为WikiPage实现存储/加载方法的方式。我也在链接视频:Robert C.Martin-Clean Architecture and Design

他所描述的(至少从我的理解来看(是,实体知道如何从某个持久层存储和加载自己的机制。当他想将WikiPages存储在内存中时,他只需重写WikiPage并创建一个新的InMemoryWikiPage。当他想把它们存储在数据库中时,他也做了同样的事情。。。

所以,我的一个问题是-这种方法叫什么我一直在学习知识库模式之类的东西,为什么像这样的课程应该是无知的,但我似乎找不到任何关于他做的这件事的材料。因为我的应用程序将由模块组成,我认为这可能有助于解决我的问题,而无需为我的实体创建一些集中存储。。。每个模块都会简单地照顾自己,包括实体的持久性。

我认为代码应该是这样的:

public class Person : IEntity
{
   public int ID { get;set; }
   public string Name { get;set; }
   public void Save()
   {
       ..
   }
   public void Update()
   {
   }
   public void Delete()
   {
   }
   ...
}

看起来有点奇怪,但是。。。或者我误解了他在视频中说的话?

我的第二个问题是,如果你不同意这种方法,在这种模块化应用程序中,你会走什么路

如果可能的话,请举例说明。

解耦的架构

我会回答您的第二个问题。我想你也会对Dependency Injection感兴趣。

我不是DI方面的专家,但我会尽力解释清楚。

首先,来自维基百科:

依赖项注入是一种软件设计模式,它允许删除硬编码的依赖项,并允许在运行时或编译时更改它们。

依赖注入模式的主要目的是允许在运行时,或通过配置文件,而不是在编译时,在给定依赖接口的多个实现之间进行选择。

有许多库可以帮助您实现这种设计模式:AutoFac、SimpleInjector、Ninject、Spring.NET和许多其他库。

理论上,这就是您的代码的样子(AutoFac示例(

var containerBuilder = new ContainerBuilder();
//This is your container builder. It will be used to register interfaces
// with concrete implementations

然后,注册接口类型的具体实现:

containerBuilder.RegisterType<MockDatabase>().As<IDatabase>().InstancePerDependency();
containerBuilder.RegisterType<Person>().As<IPerson>().InstancePerDependency();

在这种情况下,InstancePerDependency意味着无论何时尝试解析IPerson,都会得到一个新实例。例如,它可以是SingleInstance,所以无论何时尝试解析IPerson,都会得到相同的共享实例。

然后你构建你的容器,并使用它:

 var container = containerBuilder.Build();
 
 IPerson myPerson = container.Resolve<IPerson>(); //This will retrieve the object based on whatever implementation you registered for IPerson
 myPerson.Id = 1;
 myPerson.Save(); //Save your changes

我在这个例子中使用的模型:

interface IEntity
{            
    int Id { get; set; }            
    string TableName { get; }
    //etc
}
interface IPerson: IEntity
{
    void Save();
}
interface IDatabase
{
    void Save(IEntity entity);
}
class SQLDatabase : IDatabase
{
    public void Save(IEntity entity)
    {
        //Your sql execution (very simplified)
        //yada yada INSERT INTO entity.TableName VALUES (entity.Id)
        //If you use EntityFramework it will be even easier
    }
}
class MockDatabase : IDatabase
{
    public void Save(IEntity entity)
    {
        return;
    }
}
class Person : IPerson
{
    IDatabase _database;
    public Person(IDatabase database)
    {
        this._database = database;
    }
    public void Save()
    {
        _database.Save(this);
    }
    public int Id
    {
        get;
        set;
    }
    public string TableName
    {
        get { return "Person"; }
    }
}

别担心,AutoFac会自动解析任何Person依赖项,例如IDatabase

这样,如果你想切换数据库,你可以简单地这样做:

containerBuilder.RegisterType<SqlDatabase>().As<IDatabase>().InstancePerDependency();

我写了一个过于简化(不适合使用(的代码,它只是一个启动,谷歌";依赖注入";以获取更多信息。我希望这能有所帮助祝你好运

您发布的模式是一个活动记录。

Repository和Active Record模式的区别在于,在Active Record模式中,数据查询和持久性以及域对象都在一个类中,而在Repository中,数据持久性和查询与域对象本身是解耦的。

您可能想研究的另一种模式是查询对象,与在每个可能的查询(过滤、排序、分组等(中方法数量都会增加的存储模式不同,查询对象可以使用流畅的接口来表达[1]或专用于您可以传递参数[2]的接口

最后,您可以查看命令查询责任分离体系结构以获得一些想法。我个人大致遵循了它,只是找到了可以帮助我的想法

希望这能有所帮助。

根据评论更新

Repository模式的一个变体是

UserRepository
{
    IEnumerable<User> GetAllUsers()
    IEnumerable<User> GetAllByStatus(Status status)
    User GetUserById(int id)
    ...
}

这一个不可扩展,因为存储库被更新以用于请求的额外查询

另一种变体是将查询对象作为参数传递给数据查询

UserRepository
{
    IEnumerable<User> GetAll(QueryObject)
    User GetUserById(int id)
    ...
}

var query = new UserQueryObject(status: Status.Single)
var singleUsers = userRepo.GetAll(query)

在.Net世界中,传递Linq表达式而不是QueryObject

var singleUsers = userRepo.GetAll(user => user.Status == Status.Single)

另一种变体是将Repository专用于通过其唯一标识符对一个实体进行检索并保存,而查询对象用于提交数据检索,就像在CQRS中一样。

更新2

我建议你熟悉SOLID原则。这些原则非常有助于指导您创建一个松散耦合、高度内聚的体系结构。

Los Techies关于SOLID原理的汇编包含了关于SOLID原则的很好的介绍性文章。