使用 Java 解决实际问题

了解 Java 与 Python 和 Groovy 的不同之处,因为它被用来解决慈善机构的实际问题。
72 位读者喜欢这篇文章。
What is your favorite open source Java IDE?

Pixabay。CC0。

正如我在本系列的前两篇文章中写到的,我喜欢通过用不同的语言编写小程序来解决小问题,这样我可以比较它们解决问题的不同方法。我在本系列中使用的例子是将大宗物资分成价值相近的食品篮,分发给社区中struggling的邻居,您可以在本系列的第一篇文章中阅读相关内容

在第一篇文章中,我使用 Groovy 编程语言解决了这个问题,它在很多方面都像 Python,但在语法上更像 C 和 Java。在第二篇文章中,我用 Python 解决了这个问题,采用了非常相似的设计和努力,这证明了这两种语言之间的相似之处。

现在我将尝试用 Java 来解决。

Java 解决方案

在使用 Java 时,我发现自己会声明实用程序类来保存数据元组(新的 record 功能将非常适合这一点),而不是使用 Groovy 和 Python 中提供的语言对 map 的支持。这是因为 Java 鼓励创建将一种特定类型映射到另一种特定类型的 map,但在 Groovy 或 Python 中,拥有一个具有混合类型键和混合类型值的 map 是很酷的。

首要任务是定义这些实用程序类,第一个是 Unit

class Unit {
    private String item, brand;
    private int price;

    public Unit(String item, String brand, int price) {
        this.item = item;
        this.brand = brand;
        this.price = price;
    }
    public String getItem() { return this.item; }
    public String getBrand() { return this.brand; }
    public int getPrice() { return this.price; }

    @Override
    public String toString() { return String.format("item: %s brand: %s price: %d",item,brand,price); }
}

这里没有什么太令人惊讶的东西。我有效地创建了一个类,其实例是不可变的,因为字段 itembrandprice 没有 setter,并且它们被声明为 private。作为一般规则,除非我要改变它,否则我看不到创建可变数据结构的价值;在这个应用程序中,我看不到改变 Unit 类的任何价值。

虽然创建这些实用程序类需要更多的工作,但创建它们比仅仅使用 map 更鼓励一些设计工作,这可能是一件好事。在这种情况下,我意识到大宗包装是由许多单独的单元组成的,所以我创建了 Pack

class Pack {
    private Unit unit;
    private int count;

    public Pack(String item, String brand, int unitCount, int packPrice) {
        this.unit = new Unit(item, brand, unitCount > 0 ? packPrice / unitCount : 0);
        this.count = unitCount;
    }

    public String getItem() { return unit.getItem(); }
    public String getBrand() { return unit.getBrand(); }
    public int getUnitPrice() { return unit.getPrice(); }
    public int getUnitCount() { return count; }
    public List<Unit> unpack() { return Collections.nCopies(count, unit); }


    @Override
    public String toString() { return String.format("item: %s brand: %s unitCount: %d unitPrice: %d",unit.getItem(),unit.getBrand(),count,unit.getPrice()); }
}

Unit 类类似,Pack 类也是不可变的。这里有几点值得一提

  1. 我可以将 Unit 实例传递到 Pack 构造函数中。我选择不这样做,因为大宗包装的捆绑物理性质促使我将“单元性”视为一个内部事物,从外部不可见,但需要拆包才能暴露单元。在这种情况下,这是一个重要的决定吗?可能不是,但至少对我来说,总是要仔细考虑这种考虑。
  2. 这就引出了 unpack() 方法。Pack 类仅在您调用此方法时才创建 Unit 实例列表——也就是说,该类是惰性的。作为一般的设计原则,我发现值得决定一个类的行为应该是 eager 还是 lazy,当它看起来无关紧要时,我选择 lazy。在这种情况下,这是一个重要的决定吗?也许——这种惰性设计使得可以在每次调用 unpack() 时生成一个新的 Unit 实例列表,这可能在未来被证明是一件好事。无论如何,养成始终思考 eager 与 lazy 行为的习惯是一个好习惯。

眼尖的读者会注意到,与 Groovy 和 Python 示例中我主要关注紧凑代码,并且在设计决策上花费的时间少得多不同,在这里,我将 Pack 的定义与购买的 Pack 实例的数量分开了。同样,从设计的角度来看,这似乎是一个好主意,因为 Pack 在概念上与获得的 Pack 实例的数量完全独立。

鉴于此,我需要再添加一个实用程序类:Bought

class Bought {
    private Pack pack;
    private int count;

    public Bought(Pack pack, int packCount) {
        this.pack = pack;
        this.count = packCount;
    }

    public String getItem() { return pack.getItem(); }
    public String getBrand() { return pack.getBrand(); }
    public int getUnitPrice() { return pack.getUnitPrice(); }
    public int getUnitCount() { return pack.getUnitCount() * count; }
    public List<Unit> unpack() { return Collections.nCopies(count, pack.unpack()).stream().flatMap(List::stream).collect(Collectors.toList()); }

    @Override
    public String toString() { return String.format("item: %s brand: %s bought: %d pack(s) totalUnitCount: %d unitPrice: %d",pack.getItem(),pack.getBrand(),count,pack.getUnitCount() * count,pack.getUnitPrice()); }
}

值得注意的是

  1. 我决定将 Pack 传递到构造函数中。为什么?因为在我看来,购买的大宗包装的物理结构是外部的,而不是内部的,就像单个大宗包装的情况一样。再一次,这在这个应用程序中可能并不重要,但我相信总是要考虑这些事情是好的。如果非要说有什么不同的话,请注意我并没有固守对称性!
  2. unpack() 方法再次证明了惰性设计原则。这需要付出更多努力来生成 Unit 实例列表(而不是 Unit 实例的列表的列表,这将更容易,但需要在代码中进一步展平)。

好的!是时候继续前进并解决问题了。首先,声明购买的包装

        var packs = new Bought[] {
            new Bought(new Pack("Rice","Best Family",10,5650),1),
            new Bought(new Pack("Spaghetti","Best Family",1,327),10),
            new Bought(new Pack("Sardines","Fresh Caught",3,2727),3),
            new Bought(new Pack("Chickpeas","Southern Style",2,2600),5),
            new Bought(new Pack("Lentils","Southern Style",2,2378),5),
            new Bought(new Pack("Vegetable oil","Crafco",12,10020),1),
            new Bought(new Pack("UHT milk","Atlantic",6,4560),2),
            new Bought(new Pack("Flour","Neighbor Mills",10,5200),1),
            new Bought(new Pack("Tomato sauce","Best Family",1,190),10),
            new Bought(new Pack("Sugar","Good Price",1,565),10),
            new Bought(new Pack("Tea","Superior",5,2720),2),
            new Bought(new Pack("Coffee","Colombia Select",2,4180),5),
            new Bought(new Pack("Tofu","Gourmet Choice",1,1580),10),
            new Bought(new Pack("Bleach","Blanchite",5,3550),2),
            new Bought(new Pack("Soap","Sunny Day",6,1794),2)
        };

从可读性的角度来看,这非常好:这里有一包 Best Family Rice,包含 10 个单元,成本为 5,650(使用那些疯狂的货币单位,就像在其他示例中一样)。很容易看出,除了 10 袋大米的大宗包装外,该组织还购买了 10 包每包一袋的意大利面。实用程序类在幕后做了一些工作,但这在这一点上并不重要,因为设计工作非常出色!

请注意,这里使用了 var 关键字;它是最新 Java 版本中的一个不错的特性,通过让编译器从右侧表达式的类型推断变量的数据类型,帮助使该语言不那么冗长(该原则称为 DRY—不要重复自己)。这看起来有点类似于 Groovy 的 def 关键字,但由于 Groovy 默认是动态类型的,而 Java 是静态类型的,因此 Java 中由 var 推断的类型信息在变量的整个生命周期内都持续存在。

最后,值得一提的是,这里的 packs 是一个数组,而不是 List 实例。如果您要从单独的文件中读取此数据,您可能更喜欢将其创建为列表。

接下来,拆开大宗包装。由于 Pack 实例的拆包被委托到 Unit 实例列表中,您可以像这样使用它

        var units = Stream.of(packs)
            .flatMap(bought -> {
                return bought.unpack().stream(); })
            .collect(Collectors.toList());

这使用了一些在较新 Java 版本中引入的不错的函数式编程特性。将先前声明的数组 packs 转换为 Java 流,将 flatmap() 与 lambda 一起使用以展平 Bought 类的 unpack() 方法生成的单元子列表,并将生成的流元素收集回列表中。

与 Groovy 和 Java 解决方案一样,最后一步是将单元重新包装到食品篮中以进行分发。这是代码——它并不比 Groovy 版本更冗长(令人厌烦的分号除外),也没有什么太大的不同

        var valueIdeal = 5000;
        var valueMax = Math.round(valueIdeal * 1.1);
        var rnd = new Random();
        var hamperNumber = 0;                         // [1]

        while (units.size() > 0) {                    // [2]
            hamperNumber++;
            var hamper = new ArrayList<Unit>();
            var value = 0;                            // [2.1]
            for (boolean canAdd = true; canAdd; ) {   // [2.2]
                var u = rnd.nextInt(units.size());            // [2.2.1]
                canAdd = false;                               // [2.2.2]
                for (int o = 0; o < units.size(); o++) {      // [2.2.3]
                    var uo = (u + o) % units.size();
                    var unit = units.get(uo);                      // [2.2.3.1]
                    if (units.size() < 3 ||
                            !hamper.contains(unit) &&
                            (value + unit.getPrice()) < valueMax) { // [2.2.3.2]
                        hamper.add(unit);
                        value += unit.getPrice();
                        units.remove(uo);                           // [2.2.3.3]
                        canAdd = units.size() > 0;
                        break;                                      // [2.2.3.4]
                    }
                }
            }                                                // [2.2.4]
            System.out.println();
            System.out.printf("Hamper %d value %d:\n",hamperNumber,value);
            hamper.forEach(unit -> {
                System.out.printf("%-25s%-25s%7d\n", unit.getItem(), unit.getBrand(),
                       unit.getPrice());
            });                                                      // [2.3]
            System.out.printf("Remaining units %d\n",units.size());  // [2.4]

一些说明,上面注释中的括号中的数字(例如,[1])对应于下面的说明

  • 1. 设置要加载到任何给定食品篮中的理想值和最大值,初始化 Java 的随机数生成器和食品篮编号。
  • 2. 只要有更多可用单元,此 while {} 循环就会将单元重新分配到食品篮中
    • 2.1 递增食品篮编号,获取一个新的空食品篮(Unit 实例列表),并将其值设置为 0。
    • 2.2 此 for {} 循环将尽可能多地向食品篮添加单元
      • 2.2.1 获取零到剩余单元数减 1 之间的随机数。
      • 2.2.2 假设您找不到更多要添加的单元。
      • 2.2.3 此 for {} 循环,从随机选择的索引开始,将尝试查找可以添加到食品篮中的单元。
        • 2.2.3.1 找出要查看哪个单元。
        • 2.2.3.2 如果只剩下几个单元,或者如果食品篮的价值在添加单元后不会太高并且该单元尚未在食品篮中,则将此单元添加到食品篮中。
        • 2.2.3.3 将单元添加到食品篮中,将食品篮价值增加单元价格,并从可用单元列表中删除该单元。
        • 2.2.3.4 只要还有单元剩余,您就可以添加更多,因此跳出此循环以继续查找。
      • 2.2.4 在退出此 for {} 循环时,如果您检查了每个剩余单元但找不到可以添加到食品篮中的单元,则食品篮已完成;否则,您找到了一个,可以继续寻找更多。
    • 2.3 打印出食品篮的内容。
    • 2.4 打印出剩余单元信息。

当您运行此代码时,输出看起来与 Groovy 和 Python 程序的输出非常相似

Hamper 1 value 5465:
Tofu                     Gourmet Choice              1580
Bleach                   Blanchite                    710
Coffee                   Colombia Select             2090
Flour                    Neighbor Mills               520
Sugar                    Good Price                   565
Remaining units 150

Hamper 2 value 5482:
Sardines                 Fresh Caught                 909
Tomato sauce             Best Family                  190
Vegetable oil            Crafco                       835
UHT milk                 Atlantic                     760
Chickpeas                Southern Style              1300
Lentils                  Southern Style              1189
Soap                     Sunny Day                    299
Remaining units 143

Hamper 3 value 5353:
Soap                     Sunny Day                    299
Rice                     Best Family                  565
UHT milk                 Atlantic                     760
Flour                    Neighbor Mills               520
Vegetable oil            Crafco                       835
Bleach                   Blanchite                    710
Tomato sauce             Best Family                  190
Sardines                 Fresh Caught                 909
Sugar                    Good Price                   565
Remaining units 134

…

Hamper 23 value 5125:
Sardines                 Fresh Caught                 909
Rice                     Best Family                  565
Spaghetti                Best Family                  327
Lentils                  Southern Style              1189
Chickpeas                Southern Style              1300
Vegetable oil            Crafco                       835
Remaining units 4

Hamper 24 value 2466:
UHT milk                 Atlantic                     760
Spaghetti                Best Family                  327
Vegetable oil            Crafco                       835
Tea                      Superior                     544
Remaining units 0

最后一个食品篮的内容和价值被缩写了。

结束语

“工作代码”与 Groovy 原版的相似之处显而易见——Groovy 和 Java 之间的密切关系显而易见。Groovy 和 Java 在一些方面有所不同,这些方面是在 Groovy 发布后添加到 Java 中的,例如 vardef 关键字,以及 Groovy 闭包和 Java lambda 之间表面上的相似之处和差异。此外,整个 Java 流框架为 Java 平台增加了极大的功能和表达能力(完全公开,以防它不明显——我只是 Java 流领域的新手)。

Java 将 map 用于将单一类型的实例映射到另一种单一类型的实例的意图促使您使用实用程序类或元组,而不是 Groovy map(它们基本上只是 Map<Object,Object> 加上大量的语法糖,以消除您在 Java 中会创建的那种类型转换和 instanceof 麻烦)或 Python 中更灵活的固有意图。这样做的好处是有机会对这些实用程序类应用一些真正的设计工作,这至少在程序员中灌输良好的习惯方面是有回报的。

除了实用程序类之外,与 Groovy 代码相比,Java 代码中没有太多额外的仪式或样板代码。好吧,除了您需要添加一堆导入并将“工作代码”包装在一个类定义中,这可能看起来像这样

import java.lang.*;
import java.util.*;
import java.util.Collections.*;
import java.util.stream.*;
import java.util.stream.Collectors.*;
import java.util.Random.*;

public class Distribute {

    static public void main(String[] args) {
        // the working code shown above
    }
}
class Unit { … }
class Pack { … }
class Bought { … }

在 Java 中,与在 Groovy 和 Python 中一样,当涉及到从 Unit 实例列表中抓取食品篮的东西时,需要相同的繁琐之处,包括随机数、循环遍历剩余单元等等。

另一个值得一提的问题——这不是一种特别有效的方法。从 ArrayLists 中删除元素、对重复表达式的粗心大意以及其他一些事情使得这不太适合解决大型重新分配问题。我在这里更加小心地坚持使用整数数据。但至少它执行起来非常快。

是的,我仍然在使用可怕的 while { … }for { … }。我仍然没有想到一种将 map 和 reduce 风格的流处理与随机选择单元进行重新包装结合使用的方法。你能想到吗?

请继续关注本系列的下一篇文章,其中包含 JuliaGo 版本。

接下来阅读什么
标签
Chris Hermansen portrait Temuco Chile
自从 1978 年毕业于不列颠哥伦比亚大学以来,我几乎一直离不开各种计算机,自 2005 年以来一直是全职 Linux 用户,1986 年至 2005 年一直是全职 Solaris 和 SunOS 用户,在此之前是 UNIX System V 用户。

4 条评论

Bought、Pack 和 Unit 类不是 100% 样板代码吗!?
那里没有业务逻辑。
有趣的部分(逻辑)是在大的 while 循环中。

package OOP;

public class Oop
{

public static void main(String[] args)
{
Units units = new Units();
units.add("Rice","Best Family",10,5650,1);
units.add("Spaghetti","Best Family",1,327,10);
units.add("Sardines","Fresh Caught",3,2727,3);
units.add("Chickpeas","Southern Style",2,2600,5);
units.add("Lentils","Southern Style",2,2378,5);
units.add("Vegetable oil","Crafco",12,10020,1);
units.add("UHT milk","Atlantic",6,4560,2);
units.add("Flour","Neighbor Mills",10,5200,1);
units.add("Tomato sauce","Best Family",1,190,10);
units.add("Sugar","Good Price",1,565,10);
units.add("Tea","Superior",5,2720,2);
units.add("Coffee","Colombia Select",2,4180,5);
units.add("Tofu","Gourmet Choice",1,1580,10);
units.add("Bleach","Blanchite",5,3550,2);
units.add("Soap","Sunny Day",6,1794,2);

var valueIdeal = 5000;
var valueMax = Math.round(valueIdeal * 1.1);

while (units.notEmpty())
{
Hamper hamper = new Hamper(valueMax);
while(units.tryToAddUnitsToHamper(hamper));
hamper.print();
System.out.printf("Remaining units %d\n\n",units.size());
}
}
}

****************

package OOP;

import java.util.ArrayList;

public class Units
{
private List units = new ArrayList<>();
private static Random rnd = new Random();

public void add(String item, String brand, int unitCount, int overalPrice, int packs)
{
for(int i=0; i0;
}

public boolean tryToAddUnitsToHamper(Hamper hamper)
{
boolean canAdd = false;
var u = rnd.nextInt(units.size());
for (int o = 0; o < units.size(); o++)
{
var uo = (u + o) % units.size();
var unit = units.get(uo);
if ((units.size() < 3) || unit.canAddToHamper(hamper))
{
unit.addToHamper(hamper);
units.remove(uo);
canAdd = units.size() > 0;
break;
}
}
return canAdd;
}

public int size()
{
return units.size();
}
}

*******************

package OOP;

class Unit
{
private String item, brand;
private int price;

public Unit(String item, String brand, int price)
{
this.item = item;
this.brand = brand;
this.price = price;
}

public void addToHamper(Hamper hamper)
{
hamper.addUnit(this, this.price);
}

@Override
public String toString() { return String.format("%-25s%-25s%7d",item,brand,price); }

public boolean canAddToHamper(Hamper hamper)
{
return hamper.canAdd(this,price);
}
}

*******************

package OOP;

import java.util.ArrayList;

public class Hamper
{
private static int number = 0;
private List units = new ArrayList();
private long value;
private long size;

public Hamper(long size)
{
this.size = size;
number++;
}

public void addUnit(Unit unit,int price)
{
units.add(unit);
value+=price;
}

public void print()
{
System.out.printf("Hamper %d value %d:\n\n",number,value);
for(Unit unit:units)
System.out.println(unit);
}

public boolean canAdd(Unit unit,int price)
{
return((!units.contains(unit)) && ((value + price) < size));
}

}

************************

IMHO 更多 (OOP) 可读代码
(也没有会违反信息隐藏的 getter 方法)。

迟来的感谢您的评论,Bernd。我认为您的阐述中缺少一些东西,例如在您的 Units 类中,您的 for 循环在做任何事情之前就有点结束了。在任何情况下,我想我们可以争论重构像这里的 while 循环这样的紧凑代码是否真的有什么好处。

我希望的是,一些函数式编程高手会想出一种函数式替代方案来代替我的 while 循环(它不会修改其输入并且是高效的)。

此外,恕我直言,我完全不同意您关于“会违反信息隐藏的 getter 方法”的评论 - 关键在于,对于任何人来说,无论是定义类的人还是以后使用类的人,都没有任何可维护性好处可以暴露内部实现细节。相反,最好通过 getter 和 setter,也许通过接口来定义合同,这让类维护者可以自由地在必要时更改内部结构。

这被称为“统一访问原则”。您可以在很多地方阅读到它,例如这里

https://en.wikipedia.org/wiki/Uniform_access_principle

在许多不同的方法中,减轻样板 getter/setter 的痛苦的方法是 Project Lombok,它使用注释来自动生成 getter 和 setter。Baeldung 对此有一个很好的实践概述

https://www.baeldung.com/intro-to-project-lombok

回复 作者:bschatz

好的,所以我可能没有理解您的示例的意图。

关于 getter 和 setter:评论不是关于*您如何*暴露类的内部结构,而是关于*您暴露了它*。

例如 --> https://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html

感谢您的评论,Bernd。很高兴与读者互动,非常感谢。

让我们首先回顾一下我们试图完成什么 - 我们去一家大宗商品商店购买了一堆大宗包装。我们将它们带回办公室。我们拆开大宗包装,并将单元重新分配到价值大致相等的食品篮中,然后我们将这些食品篮分发给邻居。

让我们看一下 Golub 的原则“让对象做工作”,以及我的类如何符合或不符合。

Pack 类有一个名为 unpack() 的方法。这完全遵循 Golub 的原则 - 这就是我告诉 Pack 给我一个其组成 Unit 实例列表的方式。

同样,Bought 也有一个 unpack() 方法,它给了我购买的 Pack 实例数量中所有 Unit 实例的列表。因此再次遵循 Golub 的规则。

在这一点上,我们可以注意到我们实际上购买了一些自拆包的大宗物料,将它们放在办公室的地板上,并告诉它们自行拆包;我们最终得到一个组成部分的列表,称为 Units。

让我们看一下您的链接中提到的其他规则。

“违反了封装原则” - 实际上,getter 和 setter 的目的是帮助封装。对象外部的东西无法触及对象内部;它们必须通过已知的途径进行通信。最重要的是,正如我提到的,我的类实际上是不可变的 - 没有 setter - 因此不可能注入任何东西;字段值在创建 Unit、Pack 和 Bought 实例时建立,并且不能随后更改。此外,字段值无法从外部查看,因为字段是私有的。因此,我可以以任何我想要的方式更改内部结构,只要我可以继续遵守 getter 方法建立的合同即可。Unit 本身是一个元组,仅用于封装单元概念的关键特征 - 单元是什么、谁制造的以及它的价值是什么。我们需要知道这些东西,因为我们将把这些单元放入食品篮中,而接收食品篮的人需要能够看到里面有什么,并计划如何使用它们。

“暴露了实现细节” - 同样,getter 和 setter 的目的是避免暴露实现细节。经典示例是“几何”的概念,用户需要了解一些关于封装几何的细节 - 例如,它的长度或面积 - 而无需知道它是如何存储的。除此之外,还添加了 Golub 风格的需求,例如“计算此几何与另一个几何的交集多边形”。

您提到的文章中提出的另一点是“对象是/不是简单的数据持有者”。我同意对象不仅仅是简单的数据持有者。让我们看一下 Java String 类,例如。是的,它持有一个不可变的字符串值。但它也有很多类似 getter 的方法,例如 length()、substring() 等等。

我的类 Pack 和 Bought 也不是简单的数据类。它们包括 unpack() 方法,这是我对它们唯一需要的行为。Unit 是一个简单的数据类 - 它是一个元组。在我的问题空间中,我不需要告诉 Unit 做任何事情;我只需要移动它。

我可能应该将 Unit 分发功能的 List 放在一个单独的类中,该类的构造函数接受 Unit 实例的 List,并且该方法将返回 Hamper 实例的 List。相反,我选择在运行时创建这些食品篮,只是在创建时打印出来。

顺便说一句,我认为一个糟糕的设计是将 Bought 实例的“packs”数组馈送到假设的分发功能中,因为这会违反低耦合原则。也就是说,Bought、Pack 或 Unit 中的更改可能会导致分发功能发生更改。而它的唯一“输入”是一个 Unit 实例的 List,对 Bought 或 Pack 的更改是不相关的。

回到您的评论 - getter 和 setter 的重点是它们*不*暴露您的类的内部结构。Getter 尤其如此,setter 在需要可变对象的情况下也是如此,它们封装(隐藏)了实现细节。您只能声称您知道一些正在存储的内容;而不是如何存储,也不是涉及哪种类型的计算。

似乎困扰许多人的是为类的每个私有字段自动生成 getter 和 setter 的想法。当然,这不一定是一个普遍的好主意 - 尤其是 setter,因为人们应该始终考虑一个类的实例是否应该是不可变的。当然,一个类也可能有一些仅在内部有意义的字段,并且为这种字段提供 getter,更不用说 setter,是没有意义的。

但是,假设类适合用途,并且它们只为用户提供必要的通信途径,那么有什么理由认为这种有限地使用 getter 和 setter 是不好的呢?

在我的例子中,由于我的类没有 setter,因此您不能声称您可以将任何旧数据注入到实例中。

此外,由于您无法从我的 Pack 和 Bought 类的公共方法中确定我是否保留了合并的 Unit 实例列表,或者我是否只是在调用 unpack() 时(惰性地)物化该列表,因此您不能真正声称您了解所有关于这些类的内部实现细节,仅仅因为我为您提供了等同于大宗包装标签上的 getter。

在我的 Pack 或 Bought 实例中,您可以争辩说我的 getter 是不必要的,因为我似乎不需要知道它们内部有什么。但也许我希望 Pack 具有等同于大宗包装上的“标签”的东西,它可以告诉我里面有什么,而无需拆包,或者 Bought 可以告诉我在不拆开所有大宗包装并计算组件的情况下,我总共有多少个 Unit。在任何情况下,我都没有在这个程序中使用这些方法,所以肯定可以说我不需要 getter。

这是一个不错的讨论

https://stackoverflow.com/questions/34805276/do-setter-and-getter-methods-breaks-encapsulation

回复 作者:bschatz

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.