我正在重构一个相当大的项目。我的一个里程碑是隐藏我自己的API背后的第三方API的每一个依赖。主要目标是在单元测试中允许存根和模拟,次要:通过摆脱第三方API特定的" voodoo"来提高业务逻辑的可读性。我确定你们在项目中拥有或拥有代码的那些部分,你可以使用特定于域的内容突然发现自己正处于玩弄第三方API代码的过程中,只是为了摒弃你的领域特定的问题。后来的。我想摆脱这些部分,以便更清楚地阅读和理解,维护或扩展业务逻辑。
基本上第一步已经落后于我。我摆脱了每一个第三方依赖关系并将其隐藏在我的API背后。现在它只是一个大的界面粗略地取代了对第三方的呼叫。如果有第三方" 3rdPartyAPIitem.Load()"现在调用它是" myAPI.ItemLoad(myItem)",如果有第三方" 3rdPartyAPI.DeleteItems(listOfItems,modeOfDeletion)"现在它是" myAPI.DeleteItems(listOfMyItems,myModeOfDeletion)"。我创建了界面隐藏第三方"项目"同样。我想你得到的照片。
我已经将我的API操作划分为主界面继承的更小,更集中的界面。所以对于加载项目,我有ILoader接口,为了保存我有ISaver接口等,我的主API接口继承了它们,当然还有它背后的实现。在业务逻辑代码中,只有主API和项目接口与我的一些自定义枚举一起使用,取代了第三方API。
现在我想知道是否应该重新构建我的API以提供更多的使用粒度。我的意思是,例如:当业务逻辑需要立即保存项目时,它使用我的主API接口,即使保存操作是ISaver接口的一部分。我当然可以将我的API实例发送到那里,因为ISaver和VS / Resharper甚至可以帮助找到那些地方,但是在一种方法中我保存在另一种方法中我会删除而在另一种方法中我完全做其他事情。所以它更容易"只需保留IMyAPI参考并调用它所需的内容。至少现在更容易。
好的,关于故事,请让我们看看我想问你的代码。
public interface ILoader
{
bool Load();
}
public interface ISaver
{
bool Save();
}
public interface IService : ILoader, ISaver
{
}
public class ServiceA : IService
{
public bool Load()
{
return false;
}
public bool Save()
{
return false;
}
}
public class ServiceB : IService
{
private readonly ILoader _loader;
private readonly ISaver _saver;
public ServiceB(ILoader loader, ISaver saver)
{
_loader = loader;
_saver = saver;
}
public bool Load()
{
return _loader.Load();
}
public bool Save()
{
return _saver.Save();
}
}
public interface IServiceC
{
ILoader Loader { get; }
ISaver Saver { get; }
}
public class ServiceC : IServiceC
{
public ILoader Loader { get; private set; }
public ISaver Saver { get; private set; }
public ServiceC(ILoader loader, ISaver saver)
{
Loader = loader;
Saver = saver;
}
}
ServiceA就是我现在拥有的。一个单片API,在我看来是懒惰和丑陋的"设计"我想进入另外两个实现中的一个。
ServiceB提供了依赖注入,因此可以为单元测试注入存根/模拟,我甚至需要切换到另一个第三方API,我只是"只是"需要实现ILoader和ISaver。另一方面,即使依赖关系可见,其内部使用也不清楚。我不确定这是不是一个问题,或者说不诚实,可能不是。
ServiceC与ServiceB基本相同,但它更清楚地表明整个API被划分为功能部分。需要更改客户端(我的业务逻辑)代码以适应这一点,但现在不是问题,无论如何我都要编写此代码。但是,通过在ServiceB上使用这种构造,我能获得或失去任何东西吗?
哦,我的API不仅仅是一些简单的方法。我们正在讨论几十种隐藏更多第三方方法的方法(我在重构期间已经达到的最小值)。因此,虽然我的例子是微不足道的真实的东西,但这就是为什么我会欣赏这个主题的一些提示,建议或批评。
答案 0 :(得分:1)
我个人会去ServiceB。你说“另一方面,即使依赖关系是可见的,他们的内部使用也不清楚。我不确定这是一个问题,或者说不诚实,可能不是。”我觉得这是实际上是一个优势。
让我们说当你加载时你必须做一些事情,如果你必须改变ILoader对象的使用方式,你必须要做一些事情,你必须要注意ILoader的所有实现都要调整,在B中这可能是在调用代码中加以说明。
public bool Load()
{
//something new can go here easy
return _loader.Load();
}
然而有人可能会说C更容易理解和维护......所以这可能是一个偏好的问题?
答案 1 :(得分:1)
我倾向于ServiceB
。
通过这种方式,您可以将ServiceB
实施作为ILoad
或ISave
传递。客户端代码不必知道它实现了两者。
您可以轻松地存根/模拟/使用不同的实现。
您可以在调用代理人之前或之后轻松调整包装的实现或执行操作。
您的代码会更清晰
将ServiceB与ServiceC进行比较
serviceB.load(...)
vs
serviceC.Loader.load(...)