解耦架构

5
我正在开发一个系统,希望尽可能地解耦层级。我的目标是建立一种模块化应用,可以在不严重修改系统其余部分的情况下切换数据库等。因此,我一直在观看 Robert C. Martin 的有关良好实践、清晰代码、解耦架构等方面的演讲,以获取一些灵感。但他描述了系统 Fitnesse 和他们为 WikiPage 实现存储/加载方法的方式,让我感到有点奇怪。我也链接了相关视频:Robert C. Martin - Clean Architecture and Design
从我的理解来看,他所描述的是该实体知道如何从某个持久层存储和加载自身的机制。当他想要将 WikiPages 存储在内存中时,他只需重写 WikiPage 并创建一个新的 InMemoryWikiPage。当他想把它们存储在数据库中时,他也做了同样的事情...
因此,我有一个问题:这种方法叫什么?我一直在学习 Repository 模式等内容,了解为什么类应该具有持久性无关性,但我似乎找不到任何有关他所做的这件事的资料。因为我的应用程序将由模块组成,我认为这可能有助于解决我的问题,而无需创建一些实体集中存储的需要...每个模块只需关心自己,包括其实体的持久性。
我认为代码会像这样:
public class Person : IEntity
{
   public int ID { get;set; }
   public string Name { get;set; }

   public void Save()
   {
       ..
   }

   public void Update()
   {
   }

   public void Delete()
   {
   }

   ...
}

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

我的第二个问题是,如果你不同意这种方法,在这样的模块化应用中,你会选择什么路径?

如果可能的话,请提供一个例子并加以解释。

2个回答

4
我将回答您的第二个问题。我认为您也会对“依赖注入”感兴趣。
我不是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();

我写了一个过于简化(不适合使用)的代码,只是作为一个入门指南。如果想要更多信息,请搜索“依赖注入”。希望这可以帮到你。祝好运。


3
你发布的模式是一个Active Record
仓储模式和Active Record模式的区别在于,在Active Record模式中,数据查询和持久化以及域对象位于一个类中,而在仓储模式中,数据持久化和查询与域对象本身解耦。
你可能还想了解另一个模式,即查询对象。与仓储模式不同的是,查询对象可以使用流畅的接口来表达每个可能的查询(过滤、排序、分组等),也可以使用专用的方法,其中你可以传递参数[2][1] 最后,您可以查看命令查询责任分离架构以获取想法。我个人松散地遵循它,只是拾取可以帮助我的想法。
希望这有所帮助。
根据评论更新的基础上,存储库模式的一个变体如下:
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)

另一种变体是针对唯一标识符检索一个实体并保存它的专用存储库,而查询对象用于提交数据检索,就像在CQRS中一样。
更新2
我建议您熟悉SOLID原则。这些原则有助于指导您创建松散耦合、高内聚的架构。 Los Techies关于SOLID原则的汇编包含有关SOLID原则的良好入门文章。

几点说明。Entity Framework不是一个活动记录模式的实现。而且,仓储库应该非常简单(获取、持久化、删除,没有太多其他内容)。DAO更容易有很多不同的查询方法。 - jl.
@jl,感谢您的评论,我已经修正并扩展了我的回答。 - OnesimusUnbound
谢谢你的回答!这是一些有趣的东西 :) 我只需要对你的建议进行一些研究,看看哪个最适合我。为你的努力和有用的建议点赞。在选择“答案”之前,我会等待一段时间,看看是否有其他人发表意见。我发现如果我太快选择了答案,通常没有人会再给出自己的意见。 - walther
@walther,请看我的第二次更新。我认为这就是你要找的那个。 - OnesimusUnbound

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接