我试图向我的团队解释为什么这是不好的做法,我正在寻找一个反模式参考来帮助解释。这是一个非常大的企业应用程序,所以这里有一个简单的例子来说明实现的内容:
public void ControlStuff()
{
var listOfThings = LoadThings();
var listOfThingsThatSupportX = new string[] {"ThingA","ThingB", "ThingC"};
foreach (var thing in listOfThings)
{
if(listOfThingsThatSupportX.Contains(thing.Name))
{
DoSomething();
}
}
}
我建议我们在'Things'基类中添加一个属性来告诉我们它是否支持X,因为Thing子类需要实现有问题的功能。像这样:
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
{
}
所以,很明显为什么第一种方法是不好的做法,但这又叫什么呢?另外,有没有比我建议的更适合这个问题的模式?
答案 0 :(得分:76)
通常,更好的方法(恕我直言)将使用接口而不是继承
然后只需检查对象是否已实现接口。
答案 1 :(得分:40)
我认为反模式名称是硬编码:)
是否应该ThingBase.supportsX
至少取决于X
是什么。在极少数情况下,知识可能只在ControlStuff()
。
通常情况下,X
可能是ThingBase
可能需要使用ThingBase.supports(ThingBaseProperty)
或其他类似事件公开其功能的一组内容之一。
答案 2 :(得分:22)
IMO这里的基本设计原则是封装。在您提出的解决方案中,您已将逻辑封装在Thing类中,而在原始代码中,逻辑会泄漏到调用者中。
它也违反了Open-Closed原则,因为如果你想添加支持X的新子类,你现在需要去修改包含那个硬编码列表的任何地方。使用您的解决方案,您只需添加新类,覆盖该方法即可。
答案 3 :(得分:11)
不知道一个名字(怀疑存在)但是把每个“东西”想象成一辆车 - 有些车有巡航控制系统而其他车没有。
现在您拥有自己管理的车队,并希望了解哪些车辆具有巡航控制权。
使用第一种方法就像查找所有具有巡航控制功能的车型列表,然后驾车开车并在该列表中搜索每个车型 - 如果在那里它意味着该车具有巡航控制,否则它没有。很麻烦,对吧?
使用第二种方法意味着每辆拥有巡航控制系统的汽车都贴有“我有巡航控制”的标签,您只需要查找该贴纸,而无需依赖外部信息来为您提供信息。
不是非常技术性的解释,但简单而重要。
答案 4 :(得分:7)
这种编码实践很有意义。它可能不是一个实际上支持X的问题(当然,每个东西上的界面会更好),而是支持X的东西是你想要启用的东西。您看到的标签只是配置,目前硬编码,对此的改进是最终将其移动到配置文件或其他方式。在你说服你的团队改变它之前,我会检查这不是你所转述的代码的意图。
答案 5 :(得分:7)
写太多代码反模式。这使得阅读和理解变得更加困难。
正如已经指出的那样,使用界面会更好。
基本上,程序员没有利用面向对象的原则,而是使用过程代码来做事。每当我们达到'if'语句时,我们应该问自己,我们是否应该使用OO概念而不是编写更多的程序代码。
答案 6 :(得分:5)
这只是一个糟糕的代码,它没有名称(它甚至没有OO设计)。但争论可能是第一个代码不会休Open Close Principle。支持的事物列表发生变化时会发生什么?你必须重写你正在使用的方法。
但是当你使用第二个代码片段时会发生同样的事情。让我们说支持规则发生了变化,你必须转到每个方法并重写它们。我建议你有一个抽象的支持类,并在它们改变时传递不同的支持规则。
答案 7 :(得分:4)
我认为它没有名称,但可能检查http://en.wikipedia.org/wiki/Anti-pattern的主列表是否知道? http://en.wikipedia.org/wiki/Hard_code可能看起来更接近。
我认为您的示例可能没有名称 - 而您建议的解决方案称为复合。
答案 8 :(得分:4)
由于你没有展示代码的真正含义,因此很难给你一个强大的骚动。这是一个根本不使用任何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));
}
}
答案 9 :(得分:4)
我称之为Failure to Encapsulate
。这是一个弥补的术语,但它是真实的,经常被看到
许多人忘记了封闭不仅仅是隐藏带有对象的数据,它还隐藏了该对象中的行为,或者更具体地说,隐藏了对象行为的实现方式。
通过使用正确的程序操作所需的外部DoSomething()
,您会产生许多问题。你不能在你的事物列表中合理地使用继承。如果您更改“thing”的签名,在这种情况下是字符串,则行为不会跟随。您需要修改此外部类以添加它的行为(将DoSomething()
调回到派生的thing
。
我会提供“改进的”解决方案,即拥有Thing
个对象的列表,其中包含一个实现DoSomething()
的方法,该方法可以作为无效操作的NOOP。这本地化了thing
本身的行为,并且不需要维护特殊匹配列表。
答案 10 :(得分:3)
如果它是一个字符串,我可以称之为“魔术字符串”。在这种情况下,我会考虑“魔术字符串数组”。
答案 11 :(得分:0)
我不知道是否存在用于编写不可维护或可重用的代码的“模式”。你为什么不能给他们原因呢?
答案 12 :(得分:0)
对我来说,最好是根据计算复杂性来解释它。绘制两个图表,显示count(listOfThingsThatSupportX )
和count(listOfThings )
期间所需的操作次数,并与您提出的解决方案进行比较。
答案 13 :(得分:0)
您可以使用属性,而不是使用接口。他们可能会描述对象应该被“标记”为这种对象,即使标记它不会引入任何其他功能。即一个被描述为'Thing A'的对象并不意味着所有'Thing A'都有一个特定的界面,重要的是它们是'Thing A'。这似乎是属性的工作而不是接口。