使用 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 实例列表——也就是说,该类是惰性的。作为一般设计原则,我发现值得决定一个类的行为应该是急切的还是惰性的,当它似乎无关紧要时,我选择惰性的。在这种情况下,这是一个重要的决定吗?也许——这种惰性设计使得每次调用 unpack() 时都可以生成一个新的 Unit 实例列表,这可能在将来被证明是一件好事。无论如何,养成始终思考急切与惰性行为的习惯是一个好习惯。

眼尖的读者会注意到,与 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(使用那些疯狂的货币单位,就像其他示例中一样)。很容易看出,除了 1 包 10 袋装的大米外,该组织还购买了 10 包 1 袋装的意大利面。实用程序类在幕后做了一些工作,但这在这一点上并不重要,因为设计工作很棒!

请注意,这里使用了 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));
}

}

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

恕我直言,更易读(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 规则。

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

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

“违反了封装原则”——实际上,getter 和 setter 的重点是帮助进行封装。对象外部的事物无法触及对象的内部;它们必须通过已知的途径进行通信。最重要的是,正如我所提到的,我的类实际上是不可变的——没有 setter——因此不可能注入任何东西;字段值在创建 Unit、Pack 和 Bought 实例时就已建立,并且无法随后更改。此外,字段值无法从外部查看,因为字段是私有的。因此,我可以随意更改内部结构,只要我可以继续遵守 getter 方法建立的契约。

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

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

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

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

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

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

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

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

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

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

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

这是一个不错的讨论

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

回复 作者 bschatz

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