围绕抽象类的OOP设计

时间:2018-03-18 23:30:57

标签: java oop

假设我有Namespace类如下:

abstract class Namespace {
   protected prefix;
   protected Map<String, String> tags;

   public setTag(String tagKey, String tagValue) {
      tags.put(tagKey, tagValue);
   }
}

我被告知,一个好的设计是每个命名空间都有单独的实现,其中getter定义了从tags映射中检索的信息。即

class FooNamespace extends Namespace {
   public String getNameTag() {
      return tags.get("name");
   }
}

我们可以单独实现像BarNamespace这样的命名空间,它会存储不同的标记(BarNamespace没有ex的名称标记,而是有一个年龄标记)。为什么上述设计比简单地让简单Namespace类的使用者请求他们想要的标记更有意义。即:

 class Namespace {
       private prefix;
       private Map<String, String> tags;

       public String getTag(String key) {
          return tags.get(key);
       }
    }

以上用作

Namespace.getTag("name");

2 个答案:

答案 0 :(得分:0)

答案很大程度上取决于你希望实现的目标 - 但如果我必须输入getTag("name")几百次,我一定会犯错误。

getNameTag取消了一些猜测工作,降低了我在没有注意到的情况下输入"name"错误的可能性。它还减少了我需要的关于API的知识量 - 我知道我可以获得name标签的价值,但我不需要知道它是如何实际实现的 - 它可能会实施之间的变化。

它是一个好的设计&#34;是一个意见问题,取决于如何使用目标类。是&#34;名称&#34;在应用程序中常见的有用吗?怎么样&#34; date&#34;或&#34;数字&#34;值 - 一些辅助方法可能很好;)

  

有一点让我感到困惑的是,虽然Bar没有&#34; name&#34;的访问器,但是它由一个包含原始Map的抽象Namespace类支持,因此任何人都可以推入&# 34;名称&#34;通过执行Bar.putTag标记(&#34;名称&#34;,&#34; nameValue&#34;);关于如何实施&#34; setter&#34;?

的任何想法

这也是我对集合API的一般错误。

您可以创建一个不可变且可变的概念......

public interface Namespace {
    public String getTag(String key);
}

public interface MutableNamespace extends Namespace {
    public void setTag(String key, String value);
}

然后你可以开始抽象那些......

public abstract class AbstractNamespace implements MutableNamespace {
    private prefix;
    private Map<String, String> tags;

    public setTag(String tagKey, String tagValue) {
        tags.put(tagKey, tagValue);
    }

    public String getTag(String key) {
        return tags.get(key);
    }
}

最后为可能在......中使用的上下文提供有用的实现。

public interface MySuperHelpfulNamespace extends Namespace {
    public String getNameTag();
}

public class DefaultMySuperHelpfulNamespace extends AbstractNamespace  implements MySuperHelpfulNamespace {
    public String getNameTag() {
        return tags.get("name");
    }
}

然后写你的app来支持他们......

public void someMethodWhichDoesNotNeedToChangeTheValues(MySuperHelpfulNamespace namespace) {
    //...
}

public void someMethodWhichDoesNeedToChangeTheValues(MutableNamespace namespace) {
    //...
}

这实际上是&#34;编码到接口(不是实现)

的一个例子

答案 1 :(得分:0)

问题源于......

您尝试通过继承重用Namespace的财产:Map

面向对象背后的想法是......

告诉对象执行行为。

  

What is an Object?

     

隐藏内部状态并要求通过对象的方法执行所有交互被称为数据封装 - 面向对象编程的基本原则。

     

Object

ClassA扩展ClassB时,子类型ClassA应该继承它的超类型{{1}的行为或公共接口}}。 您的ClassB类型似乎无法定义任何行为。

您可以将Namespacesetter视为行为,

但不要被愚弄。目前没有理由让开发人员更喜欢使用getter类型,而不仅仅使用Namespace,而不是将他们使用的界面从Map更改为get

getTag&amp; getter方法违反了OOP。随意read up on the discussion

应该如何setter实际使用?它有什么要求?应Namespace定义哪些行为?

您可以通过分析Namespace的使用方式来回答这个问题。举个例子,也许您打算将它附加到getter以形成一些XML。

想象一下你的要求是......

  

将来自各种XML标记集的单个标记附加到将用于呈现的StringBuilder。

而不是......

StringBuilder

StringBuilder builder = ...; Namespace namespace = ...; builder.append(namespace.getTag("name")); 可以负责附加 标记NamespaceNamespace存储的所有者标签)对任何请求它。

Map

请注意public final class Namespace { private final Map<String, String> tags; public Namespace(Map<String, String> tags) { this.tags = tags; } // if you REALLY need to add tags after instantiation public Namespace addTag(String key, String value) { Map<String, String> tags = new HashMap<>(this.tags); tags.put(key, value); return new Namespace(tags); } // the behavior that fufills the requirement public void appendTo(StringBuilder builder, String key) { builder.append(tags.get(key)); } } 行为(附加到Namespace),而不是StringBuilder的代理。

Map

我没有找到创建子类型的原因(不强迫它)。只有在必须扩展超类型的行为时,才应创建子类型。如果StringBuilder builder = ...; Namespace namespace = ...; namespace.appendTo(builder, "name"); 没有向BarNamespace添加任何功能,则不需要它。

创建Namespace以涵盖帖子公开的功能后,我不需要子类型。似乎所有内容都由Namespace优雅地处理。

您没有指定您的要求,但希望此答案将指导您确定它们(基于您如何使用Namespace)并以面向对象的方式实施它们。 / p>

正如@MadProgrammer所指出的那样:

  

如果我必须输入几百次getTag(&#34; name&#34;),我一定会犯错误。

如果您发现自己需要经常输入getter,那么您可能希望包含一种类型安全的方式来执行该行为。

在这种情况下,您可能更喜欢composition over inheritance。您可以创建引用"name"的类型来执行您想要执行的操作,而不是使子类型扩展Namespace

Namespace

你可以说&#34; 等等!我想把public final class Bar { private final Namespace namespace; public Bar(Namespace namespace) { this.namespace = namespace; } public void appendNameTo(StringBuilder builder) { namespace.appendTo(builder, "name"); } } 传递给预期Bar的地方!&#34;

这没有意义。如果代码依赖于Namespace,那么即使在您的代码中也不会出现类型安全性。例如:

Namespace

除非您向void doSomething(Namespace namespace) { } 投放或声明appendNameTo,否则您无法访问Namespace中定义的任何方法。你说子类型可能有不同的标签。这意味着如果您要进行类型安全,您的子类型将具有不同的公共接口,因此我没有扩展Bar