解耦的架构
本文关键字:解耦 | 更新日期: 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原则的很好的介绍性文章。