5

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

所以,我一直在关注 Robert C. Martin 关于良好实践、简洁代码、解耦架构等的谈话之一,以获得一些灵感。我觉得有点奇怪的是他对系统的描述Fitnesse以及他们为WikiPages 实现存储/加载方法的方式。我也在链接视频: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()
   {
   }

   ...
}

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

我的第二个问题是,如果您不同意这种方法,那么在这种模块化应用程序中您将采取什么途径?

如果可能的话,请提供一个例子并给出一些解释。

4

2 回答 2

4

我会回答你的第二个问题。我想你也会对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 会自动解析任何PersonDependencies,例如IDatabase.

这样,如果您想切换数据库,您可以简单地执行以下操作:

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

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

于 2013-04-12T12:08:35.867 回答
3

您发布的模式是Active Record

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

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

最后,您可以查看命令查询职责分离架构的想法。我个人松散地遵循它,只是拿起了可以帮助我的想法。

希望这可以帮助。

根据评论更新

存储库模式的一种变体是

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 原理的良好介绍性文章。

于 2013-04-12T11:12:34.717 回答