已更新:有关我如何实现该解决方案,请参阅问题结尾。
很抱歉这个问题措辞不佳,但我不确定如何提出它才是最好的。我不确定如何设计一个可以重复使用的解决方案,其中大部分代码每次实现时都完全相同,但部分实现每次都会发生变化,但遵循相似的模式。我尽量避免复制和粘贴代码。
我们有一个内部数据消息系统,用于跨不同机器上的数据库更新表。我们正在扩展我们的消息服务以将数据发送给外部供应商,我想编写一个简单的解决方案,如果我们决定将数据发送给多个供应商,可以重复使用。该代码将被编译成一个 EXE 文件并定期运行以向供应商的数据服务发送消息。
以下是代码作用的粗略概述:
public class OutboxManager
{
private List<OutboxMsg> _OutboxMsgs;
public void DistributeOutboxMessages()
{
try {
RetrieveMessages();
SendMessagesToVendor();
MarkMessagesAsProcessed();
}
catch Exception ex {
LogErrorMessageInDb(ex);
}
}
private void RetrieveMessages()
{
//retrieve messages from the database; poplate _OutboxMsgs.
//This code stays the same in each implementation.
}
private void SendMessagesToVendor() // <== THIS CODE CHANGES EACH IMPLEMENTATION
{
//vendor-specific code goes here.
//This code is specific to each implementation.
}
private void MarkMessagesAsProcessed()
{
//If SendMessageToVendor() worked, run this method to update this db.
//This code stays the same in each implementation.
}
private void LogErrorMessageInDb(Exception ex)
{
//This code writes an error message to the database
//This code stays the same in each implementation.
}
}
我想以这样一种方式编写此代码,即我可以重新使用未更改的部分,而不必诉诸复制和粘贴并填写 SendMessagesToVendor() 的代码.我希望开发人员能够使用 OutboxManager 并编写所有已编写的数据库代码,但被迫提供他们自己的向供应商发送数据的实现。
我确信有很好的面向对象原则可以帮助我解决这个问题,但我不确定最好使用哪一个。
这是我最终采用的解决方案,灵感来自 Victor's answer和 Reed's answer (and comments)使用接口(interface)模型。所有相同的方法都在那里,但现在它们隐藏在用户可以在必要时更新的界面中。
直到我意识到我允许类的使用者插入他们自己的类以进行数据访问 (IOutboxMgrDataProvider) 和错误记录 (IErrorLogger)。虽然我仍然提供默认实现,因为我不希望这段代码发生变化,但消费者仍然可以用自己的代码覆盖它们。除了写出多个构造函数(I may change to named and optional parameters)之外,更改我的实现确实没有花很多时间。
public class OutboxManager
{
private IEnumerable<OutboxMsg> _OutboxMsgs;
private IOutboxMgrDataProvider _OutboxMgrDataProvider;
private IVendorMessenger _VendorMessenger;
private IErrorLogger _ErrorLogger;
//This is the default constructor, forcing the consumer to provide
//the implementation of IVendorMessenger.
public OutboxManager(IVendorMessenger messenger)
{
_VendorMessenger = messenger;
_OutboxMgrDataProvider = new DefaultOutboxMgrDataProvider();
_ErrorLogger = new DefaultErrorLogger();
}
//... Other constructors here that have parameters for DataProvider
// and ErrorLogger.
public void DistributeOutboxMessages()
{
try {
_OutboxMsgs = _OutboxMgrDataProvider.RetrieveMessages();
foreach om in _OutboxMsgs
{
if (_VendorMessenger.SendMessageToVendor(om))
_OutboxMgrDataProvider.MarkMessageAsProcessed(om)
}
}
catch Exception ex {
_ErrorLogger.LogErrorMessage(ex)
}
}
}
//...interface code: IVendorMessenger, IOutboxMgrDataProvider, IErrorLogger
//...default implementations: DefaultOutboxMgrDataProvider(),
// DefaultErrorLogger()
最佳答案
有两种非常简单的方法:
将 OutboxManager 设为抽象类,并为每个供应商提供一个子类。 SendMessagesToVendor 可以标记为抽象的,强制每个供应商重新实现它。这种方法很简单,非常符合 OO 原则,并且还具有允许您为其他方法提供实现的优点,但如果您以后想要允许的话,仍然允许覆盖供应商特定版本。
让 OutboxManager 封装一些其他类或接口(interface),这些类或接口(interface)提供 SendMessagesToVendor 中所需的供应商特定信息。这可以很容易地成为每个供应商实现的一个小接口(interface),SendMessagesToVendor 可以使用这个接口(interface)实现来发送它的消息。这样做的好处是允许您在此处编写一些代码——可能会减少供应商之间的重复。它还可能使您的 SendMessagesToVendor 方法更加一致,并且更容易测试,因为您只需要依赖此处所需的特定供应商功能。这也可能实现为作为相关(但略有不同)方法传入的委托(delegate)(不过,我个人更喜欢通过委托(delegate)实现接口(interface))。
关于c# - 在这种特定情况下,我应该如何建模我的代码以最大限度地重用代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3389588/
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
这是在Ruby中设置默认值的常用方法:classQuietByDefaultdefinitialize(opts={})@verbose=opts[:verbose]endend这是一个容易落入的陷阱:classVerboseNoMatterWhatdefinitialize(opts={})@verbose=opts[:verbose]||trueendend正确的做法是:classVerboseByDefaultdefinitialize(opts={})@verbose=opts.include?(:verbose)?opts[:verbose]:trueendend编写Verb
我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha