这个坏习惯/反模式的名字是什么?
我正在向我的团队解释为什么这是不好的做法,并且正在寻找反模式参考来帮助我解释。 这是一个非常大的企业应用程序,所以下面是一个简单的例子来说明实现的内容:
public void ControlStuff() { var listOfThings = LoadThings(); var listOfThingsThatSupportX = new string[] {"ThingA","ThingB", "ThingC"}; foreach (var thing in listOfThings) { if(listOfThingsThatSupportX.Contains(thing.Name)) { DoSomething(); } } }
我build议我们添加一个属性到'Things'基类来告诉我们它是否支持X,因为Thing子类需要实现这个function。 像这样的东西:
public void ControlStuff() { var listOfThings = LoadThings(); foreach (var thing in listOfThings) { if (thing.SupportsX) { DoSomething(); } } } class ThingBase { public virtual bool SupportsX { get { return false; } } } class ThingA : ThingBase { public override bool SupportsX { get { return true; } } } class ThingB : ThingBase { }
所以,这很明显,为什么第一种方法是不好的做法,但这叫做什么? 另外,是否有比我build议的更适合这个问题的模式?
通常一个更好的方法(恕我直言)将使用接口,而不是inheritance
那么这只是检查对象是否实现了接口的问题。
我认为反模式名称是硬编码 🙂
是否应该有一个ThingBase.supportsX
至less在某种程度上取决于X
是什么。 在less数情况下,这些知识可能只在ControlStuff()
。
更通常的情况是, X
可能是ThingBase
可能需要使用ThingBase.supports(ThingBaseProperty)
等公开其functionThingBase.supports(ThingBaseProperty)
。
IMO在这里扮演的基本devise原则是封装。 在你提出的解决scheme中,你已经封装了Thing类中的逻辑,在原始代码中逻辑泄漏到调用者中。
它也违反了开放 – 封闭的原则,因为如果你想添加新的支持X的子类,你现在需要去修改包含硬编码列表的任何地方。 用你的解决scheme,你只需添加新的类,重写方法,你就完成了。
不知道一个名字(怀疑是否存在),但把每件“事情”想象成一辆汽车 – 有些汽车有巡航控制系统,有些则没有。
现在你有pipe理的车队,想知道哪些巡航控制。
使用第一种方法就像查找所有具有巡航控制的车型的列表,然后开车开车并search该列表中的每一个 – 如果这意味着该车具有巡航控制,否则它不具有巡航控制。 累赘吧?
使用第二种方法意味着每辆有巡航控制的汽车都会贴上一个标有“我有巡航控制系统”的标签,而您只需要寻找那个标签,而不依靠外部来源为您提供信息。
不是非常技术性的解释,但简单而重要。
这种编码实践是有道理的,这是一个非常合理的情况。 这可能不是什么事情实际上支持X(当然每个事物上的接口会更好),而是支持X的是那些你想要启用的东西 。 你所看到的标签就是简单的configuration ,目前是硬编码的 ,改进就是把它最终移到一个configuration文件或其他地方。 在你说服你的团队去改变它之前,我会检查一下这不是你所说的代码的意图。
编写太多的代码反模式。 这使得阅读和理解变得更加困难。
正如已经指出的那样,使用接口会更好。
基本上,程序员没有利用面向对象的原则,而是使用程序代码来做事情。 每当我们达到“如果”的陈述,我们应该问自己,如果我们不应该使用面向对象的概念,而不是写更多的程序代码。
这只是一个不好的代码,它没有一个名称(它甚至没有OOdevise)。 但是这个观点可能是第一个代码并没有停止开放原则 。 受支持事物列表发生变化时会发生什么? 你必须重写你正在使用的方法。
但是当你使用第二个代码片段时会发生同样的事情。 比方说支持规则的变化,你必须去每个方法,并重写它们。 我build议你有一个抽象的支持类,并通过不同的支持规则,当他们改变。
我不认为它有一个名字,但可能在http://en.wikipedia.org/wiki/查看主列表。反模式知道吗?; http://en.wikipedia.org/wiki/Hard_code可能看起来更接近。;
我认为你的例子可能没有名字,而你提出的解决scheme是叫做Composite 。
既然你不显示代码的真正是什么,很难给你一个强有力的感觉。 这是一个不使用任何if
子句。
// invoked to map different kinds of items to different features public void BootStrap { featureService.Register(typeof(MyItem), new CustomFeature()); } // your code without any ifs. public void ControlStuff() { var listOfThings = LoadThings(); foreach (var thing in listOfThings) { thing.InvokeFeatures(); } } // your object interface IItem { public ICollection<IFeature> Features {get;set;} public void InvokeFeatues() { foreach (var feature in Features) feature.Invoke(this); } } // a feature that can be invoked on an item interface IFeature { void Invoke(IItem container); } // the "glue" public class FeatureService { void Register(Type itemType, IFeature feature) { _features.Add(itemType, feature); } void ApplyFeatures<T>(T item) where T : IItem { item.Features = _features.FindFor(typof(T)); } }
我会称之为Failure to Encapsulate
。 这是一个造词,但它是真实的,经常看到
很多人忘记了封装不仅仅是隐藏数据与对象,还隐藏了对象内部的行为,或者更具体地说隐藏了对象的行为是如何实现的。
通过具有正确的程序操作所需的外部DoSomething()
,会产生很多问题。 你不能在你的列表中合理地使用inheritance。 如果你改变“东西”的签名,在这种情况下string,行为不遵循。 你需要修改这个外部类来添加它的行为(调用DoSomething()
回到派生的thing
。
我会提供一个“改进”的解决scheme,它有一个Thing
对象列表,一个实现DoSomething()
的方法,它作为一个NOOP来处理那些什么都不做的事情。 这将本身的行为本地化,并且不需要维护特殊的匹配列表。
如果是一个string,我可以称之为“魔术string”。 在这种情况下,我会考虑“魔法string数组”。
我不知道是否有一个“模式”来编写不可维护或可重用的代码。 你为什么不能给他们理由呢?
为了我,最好的解释就是计算的复杂性。 绘制两个图表,显示count(listOfThingsThatSupportX )
和count(listOfThings )
所需的操作次数,并与您提出的解决scheme进行比较。
您可以使用属性来代替使用接口。 他们可能会描述这个对象应该被标记为这种对象,即使这样标记也不会引入任何附加function。 即一个被形容为“事物A”的对象并不意味着所有的事物A都有一个特定的接口,这只是“事物A”的重要性。 这似乎比接口更像属性的工作。