使用 Java 解决现实世界的问题

了解如何使用 Java 解决慈善机构的现实世界问题,以及 Java 与 Python 和 Groovy 的不同之处。
72 位读者喜欢这篇文章。
What is your favorite open source Java IDE?

Pixabay。CC0。

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

在第一篇文章中,我使用 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。在这种情况下,这是一个重要的决定吗?也许——这种 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() 方法再次展示了 lazy 设计原则。这需要付出更多努力来生成 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)
        };

从可读性的角度来看,这非常棒:有一个包装,里面是最佳家庭大米,包含 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 版本。

接下来阅读什么

我为什么使用 Java

可能存在比 Java 更好的语言,具体取决于工作要求。但我还没有看到任何东西能让我离开它。

标签
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 更多(面向对象编程)可读代码
(也没有会违反信息隐藏的 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

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.