我正在开发一个系统,我希望尽可能地将我的图层分离,你知道,某种模块化的应用程序能够切换数据库和东西而不需要对其余部分进行认真修改系统
所以,我一直在关注x-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()
{
}
...
}
似乎有点奇怪,但......或许我误解了他在视频中所说的内容?
我的第二个问题是,如果您不同意这种方法,您在这种模块化应用程序中应采取的路径是什么?
如果可能的话,请提供一些例子并提供一些解释。
答案 0 :(得分: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会自动解析任何Person
依赖项,例如IDatabase
。
这样,如果你想切换你的数据库,你可以这样做:
containerBuilder.RegisterType<SqlDatabase>().As<IDatabase>().InstancePerDependency();
我写了一个过度简化(不适合使用)的代码,它可以作为kickstart,谷歌“依赖注入”以获取更多信息。我希望这有帮助。 祝你好运。
答案 1 :(得分:3)
您发布的模式是Active Record。
Repository和Active Record Pattern之间的区别在于Active Record模式,数据查询和持久性,以及域对象在一个类中,而在Repository中,数据持久性和查询与域对象本身分离。
您可能想要查看的另一个模式是查询对象,与存储库模式不同,其中每个可能的查询(过滤,排序,分组等)中的方法数量都会增加,查询对象可以使用流畅的接口富有表现力的[1]或专注于您可以传递参数[2]
的人最后,您可以查看Command Query Responsibility Segregation架构的想法。我个人松散地跟着它,只是选择了可以帮助我的想法。
希望这会有所帮助。
根据评论更新
存储库模式的一个变体就是这个
UserRepository
{
IEnumerable<User> GetAllUsers()
IEnumerable<User> GetAllByStatus(Status status)
User GetUserById(int id)
...
}
此版本不会扩展,因为存储库已获得更新以获取请求的其他查询
另一种变体是将query object作为参数传递给数据查询
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 principles。这些原则非常有助于指导您创建松散耦合,高度凝聚的架构。
Los Techies compilation on SOLID pricples包含有关SOLID原则的优秀介绍性文章。