【问题标题】:Java Encapsulation & OOP Best PracticeJava 封装和 OOP 最佳实践
【发布时间】:2013-09-26 16:30:23
【问题描述】:

我实现了一个名为 mobileCall 的类。我从这个类创建了几个对象,并用来自一个 XML 的值填充这个对象的 String 变量,该 XML 有几个特定人的 mobileCalls。我需要对这个人拨打的所有电话进行分组和计数(即国内电话:11 分钟;国际电话:15 分钟;数据:20 MB)

所以,我在类中实现了几个公共方法来检查调用的类型以返回 true 或 false。在主类中,我调用了这些方法来检查它们是否满足我计算特定计数器的条件。

专业人士看到我的代码并说这不是一个好的做法,而 OOP 旨在消除这种“你是什么”方法。并且有更好的方法来实现这种行为。我试图通读 OOP 和封装,但找不到更好的方法。我觉得他说得有道理。

代码示例

public class MobileCall {

    String callType;
    String callDuration;
    String callAmount;
    String callerID;
    String calleID;
    ....
    public boolean isNational(){

        if (callType.compareTo("National")==0)
            return true;
        else
            return false;
    }

    public boolean isInternational(){

        if (callType.compareTo("international")==0)
            return true;
        else
            return false;
    }
    ...
}

In Main Method

int nationalCounter;
int internationalCounter;
MobileCall mobileCall = new MobileCall();

if(mobileCall.isNational())
    nationalCounter = nationalCounter + mobileCall.getCallDuration();
else if (mobileCall.isInternational())
    internationalCounter = internationalCounter + mobileCall.getDuration();
....

【问题讨论】:

  • 我个人认为isNationalisInternational 之类的方法没有任何问题
  • 可能是专业人士的意思是您应该将MobileCall 子类化为NationalMobileCallInternationalMobileCall。但不确定是否需要,无论如何如果它只是一个国家/国际,那么使用private boolean isNational; 可能会更好。
  • @skiwi 跟我想的差不多。如果NationalMobileCallInternationalMobileCall 执行操作的方式存在显着差异,那么是的,使用子类而不是“你是什么?” 面向对象设计的。但是这样做可能会走得太远,然后你就会得到很多毫无意义的子类。
  • 一个挑剔的旁注——如果你想检查一个字符串是否相等,请使用equalscompareTo == 0 有点 C.
  • 另一个挑剔:if (X) then return true; else return false; 在 Java 中从来没有必要;只需return X; 就足够了。 (在 X 不必是 boolean 的其他语言中,可能需要这样的代码。)

标签: java oop encapsulation


【解决方案1】:
private String callType;

....
public boolean isNational(){

    if (callType.compareTo("National")==0)
        return true;
    else
        return false;
}

public boolean isInternational(){

    if (callType.compareTo("international")==0)
        return true;
    else
        return false;
}

是相关代码。这就是重点(现在callType 已被强封装)。

三个月后,有人抱怨程序占用了太多内存。您重新访问您的代码并确定字符串可能不是确定呼叫国籍的最佳方式,毕竟它要么是国际的,要么不是国际的,对吧?这意味着您最多需要一位来存储数据。

你做出改变

private boolean nationalCall;

然后你改变使用这些数据的方法

public boolean isNational() {
  return nationalCall;
}

public boolean isInternational() {
  return !nationalCall;
}

而且由于您正确封装了“国内通话数据”,您可以放心,您不再需要搜索程序的其余部分来确保程序的其他部分能够正常工作。更改的“效果”被“封装”在类“接口”的“边界”上,实际上由许多“成员方法”实现。

现在是这样的代码

if (mobileCall.isNational()) {
    nationalCounter = nationalCounter + mobileCall.getCallDuration();
} else if (mobileCall.isInternational()) {
    internationalCounter = internationalCounter + mobileCall.getDuration();
}

看起来很可疑。请注意,这段代码似乎非常关注不同类的数据和方法。也许是在外部对班级进行管理,以至于行为的位置错位了?

如果您将方法添加到“调用”类

public int getNationalMinutes() {
  if (national) {
    return callDuration;
  }
  return 0;
}

public int getInternationalMinutes() {
  if (!national) {
    return callDuration;
  }
  return 0;
}

那么你可以将你的累积代码减少到

while (Call call : client.getAllCalls()) {
  internationalMinutes += call.getInternationalMinutes();
  nationalMinutes += call.getNationalMinutes();
}

请注意,这最后一点工作并不是严格数据意义上的 100% 封装,因为我们添加了接口项(方法);但它确实在行为意义上做了一些封装,因为我们不再需要根据一系列查询来确定通话时间是国际的还是国内的。

对一个类进行一系列检查以确定如何使用该类的日期是“调用”类之外的行为的外部迁移。因此,将这种行为移回类中可能被认为是对类行为的封装,不像前面的例子,它是对类数据的严格封装。

很多时候,人们忘记了类同时封装了数据行为。感谢您找到一个很好的例子,其中两个封装点都可以在同一个问题中提出。

【讨论】:

  • 那是一篇非常有益的帖子。谢谢!!
  • Edwin,我开始实施这些出色的建议,我有一个问题想与您分享,以了解您的想法。我正在解决使用“call.getInternationalMinutes();”封装行为的最后一个建议。在没有封装行为并且在另一个类中进行评估的第一个版本中,我会使用 if \ else if。所以如果调用是国际的,我不会评估它是否是国家的,并保存不必要的评估,但是,在这里我将一个接一个地评估所有方法,其中很多返回零。
  • 我对此非常挑剔,因为我评估了数千万次调用,我想知道不必要的评估是否会对性能产生负面影响。你怎么看?
  • 如果您评估 10 的数百万次调用,那么您的分支逻辑越简单,热点 JVM 优化的速度就越快。但是,如果您真的想了解性能影响,那么您无法通过查看静态代码来判断。剖析它,发现真正的瓶颈在哪里,然后按照你认为合适的方式修复它们,然后重新剖析以向自己证明你修复了它们。任何少的东西都只是猜测,这会导致关注错误的代码;而且,当使用正确的代码时,被认为更快的往往是错误的而不是正确的。
  • 如果您确实有很多方法,并且所有方法都返回零,那么您可能需要在封装中使用多态。但这只会让你的班级调用getMinutes() 取决于知道它正在处理什么类型的班级。也许使用多态是一个好的设计,也许不是。这将在很大程度上取决于如何使用该类。如果所有分钟都相等,那么 polymorphisim 肯定会获胜,但听起来你有不同类型的分钟(如国际、国内等),所以不清楚 polymorphisim 是赢家。祝你好运。
【解决方案2】:

isNational 这样的方法非常适合确定对象的状态,但如果您使用它们来确定对象的类型,那么使用它可能会更好一个inherited type

public class NationalMobileCall extends MobileCall 
{
}

【讨论】:

    【解决方案3】:

    好吧,如果您想将本地电话与国际电话分开(或在更多国家/地区组中分开国际电话),它的扩展性不是很好。从 OO 的角度来看,您可以创建 MobileCall 的子类型,但这将是一种矫枉过正(尤其是在没有与不同类型关联的功能的情况下)。

    我会创建一个枚举

    public enum CallType { NATIONAL, INTERNATIONAL, DATA, ... } 
    

    并将您的字符串标识符映射到枚举值(如果您明智地选择,您无需为此做太多事情)。创建一个 MobileCallTally 类(由 Map

    mobileCallTally.inc(call.getType(), call.getAmount());
    

    (当然,您可以只使用字符串作为键并完全避免枚举,但由于它是一种“类型”,我个人喜欢将其“输入”。)

    【讨论】:

      【解决方案4】:

      这里有很多问题......如果你谈论一个好的设计,你可能需要重新编码这个东西。指出几个问题:

      我将尝试向您解释封装的含义,这似乎是您的基本疑问:

      封装意味着:我有一个目标,我被设计来执行一项工作,除了我的工作我什么都不知道。

      我不允许其他人在未通知我的情况下更改我的财产,我对我的财产拥有完全控制权,我对你从我身上得到的一切负全部责任。

      这里是我的任何对象。

      为了更清楚,一个对象必须执行一项工作,并且不能尝试做很多很多事情。

      现在你如何实现上述行为?

      您需要限制对属性的访问,为属性提供适当/受控的访问方法。

      将调用类型设为枚举,因为它们是类型和值安全检查的最佳候选者。

      如果您可以分享更多代码片段,我还有很多建议要写。

      了解 OOPS 基础知识的好链接是here

      【讨论】:

        【解决方案5】:

        首先,所有实例变量都应声明为private。这封装了特定类的每个实例变量或属性。这就是封装背后的全部思想,其中对象的属性只能在该特定类中访问。

        例如,将String callType; 更改为private String callType;

        将实例变量声明为私有的好处是可以防止其他类操纵特定对象的属性,并且可以提供安全网,使它们不会被设置为错误的值。想象一下,如果一个人有能力改变另一个人的身体属性;那绝对是荒谬的。同理,属性应该是私有的,以遵守封装的原则。

        访问属性/实例变量的方式是通过使用setter 和getter。例如:

        public void setCallType(String callType) {
            this.callType = callType;
        }
        
        public String getCallType() {
            return this.callType;
        }
        

        这样,你就遵守了 OOP 的封装原则。

        【讨论】:

          猜你喜欢
          • 2020-04-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-14
          • 2011-03-29
          • 2013-06-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多