这种不良做法/反模式的名称是什么?

时间:2011-10-06 07:01:19

标签: c# anti-patterns

我试图向我的团队解释为什么这是不好的做法,我正在寻找一个反模式参考来帮助解释。这是一个非常大的企业应用程序,所以这里有一个简单的例子来说明实现的内容:

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
{
}

所以,很明显为什么第一种方法是不好的做法,但这又叫什么呢?另外,有没有比我建议的更适合这个问题的模式?

14 个答案:

答案 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可能看起来更接近。

我认为您的示例可能没有名称 - 而您建议的解决方案称为复合

http://www.dofactory.com/Patterns/PatternComposite.aspx

答案 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'。这似乎是属性的工作而不是接口。