将类的成员全部设置为public多省事儿

最近浏览一个项目的代码时发现,其中有一些以前提交的编码,定义的一些类(不管他是POJO、DTO、DAO、PO、BO、VO、QO、ENTITY还是就是个对象,不重要。总之就是数据传输对象。),这些类他的写法非常的潇洒,属性全部是public的,没有任何的get、set方法,看起来非常干净、整洁。比如基本都像下面这样

public class InventoryVo {
    @JSONField(name = "pk")
    public String id;
    @JSONField(name = "itemName")
    public String name;
    public Integer quantity;
    public String skuCode;
    public String pid;

}

//使用的地方也非常的方便
//由其他层获取到数据tradeDetail,这里的业务层接入,然后转为了json,准备请求一个http接口.
EastInventoryVo inventoryVo = new EastInventoryVo();
inventoryVo.id="10000000001";
inventoryVo.name=tradeDetail.getProductName();
inventoryVo.quantity=tradeDetail.getQuan();
inventoryVo.skuCode=tradeDetail.getProductCode();
inventoryVo.setLocator(tradeDetail.getFormArea();
String jsonString = JSON.toJSONString(inventoryVo);

公开所有成员给外部直接访问

从业务逻辑上来看,他实现了功能,没有任何问题,看起来代码干净,丝滑,甚至我都觉得大家总是写get、set,总是搞什么封装,搞什么面向对象有点多余了,这让我蠢蠢欲动啊 wa哇哇image,必须来一下image。难道我们写那么多的私有成员,然后共有方法访问,多此一举吗??

那句话叫什么,一时编码一时爽,一直编码一直爽。不。。跑错片场了。这里真的是一时编码一时爽,维护就是火葬场

从编码规范来说

  • 没什么多说的,无论是公司内部现行的java编程规范,还是阿里的泰山版、嵩山班当中都未有明确说明不许这样。 所以说他做的很好,对吧,啊对对是的!快速完成任务,你就说屌不屌吧。 image
    然后陷入思考,为什么,为什么没有加入这一条,是因为默认大家都懂OOP,都知道面向编程的基本特点,和基本原则吧。
  • 但是,如果我们使用一些静态代码分析工具绝逼会全部提示出来。(如 SonarQube、Checkstyle)会检测未封装的字段,并提示开发者进行改进。
  • 其实很多业内的编码规范(如 Google Java Style Guide、Oracle Java Coding Conventions)都强调封装的重要性,将其作为编写高质量代码的标准之一。
  • 这历史代码没有做代码审查吗,是的,太那个了,我还没来不知道呢

从OOP的角度来看

  • 我们天天喊着封装、继承、多态,为什么要封装起来,成员全部裸奔、全部对外开放,多方便,很符合开放精神的。从眼前实现功能上来看,确实很快,但稍微想一下就会发现这种裸奔的现象,不受控制的玩法,就好比去大街道上给所有人展示自己,谁也可以来访问,随时随地上手image
  • 破坏内聚性,封装确保类的内部状态和行为是内聚的,不会因为外部的变化而频繁修改。如果这样的类被到处使用,后续想扩展和对成员变量做控制就得将使用它的地方全部修改,而无法再此类的内部完成,也就是说同时破坏了单一职责原则、开闭原则
  • 在《Java编程思想》和《Effective Java》中都有专门对类成员的可访问性做阐述,指出不使用封装所带来的弊端“要在公有类而非公有域中使用访问方法”、“使可变性最小化”。简单说就是这种类,如果是确定在内部某一个地方使用,类本身不是公有的情况下,而且本来就一个地方使用,那么采用公有的形式定义全部开放是可以的,但绝大多数情况下应该遵循封装的原则,保证类的内部状态。

比如上面的InventoryVo 这个类,我现在要对quantity做一个简单的正数判断,如何处理,在每一个调用它的地方去加一个判断吗?如果要想在类的内部实现呢,你会发现开始难受了,大家都直接访问的成员变量,又没对外提供方法,还没法处理了
在比如我现在有个需求,基本字段和InventoryVo一样,只是新增几个自己的字段,同时又不想展示skuCode、pid这些字段,如何实现??

//继承了InventoryVo,添加了自己的扩展,但是没法覆盖父类的公有字段,更不可能覆盖访问修饰符,像上面说的,要屏蔽掉几个字段,发现没办法屏蔽。总之要基于InventoryVo来做扩展变得困难。
//有人会说那你子类也定义个 String skuCode等字段,然后提供方法,控制访问不就行了?宝,那样不行,JSL已经规定子类覆盖不了父类的公有字段,你只能通过super去访问。而如果你也同样在子类定义成public的,当你是用 父类() x = new 子类() 的形式定义对象,然后用x.skuCode你猜使用的谁的?那如果在改成 子类() x = new 子类(),又使用的谁的?
public class EastInventoryVo extends InventoryVo {
    private String locator;
    private String batchNo;

    public String getLocator() {
        return this.locator;
    }
    public void setLocator(String locator) {
        this.locator = locator;
    }

    public String getBatchNo() {
        return this.batchNo;
    }
    public void setBatchNo(String batchNo) {
        this.batchNo = batchNo;
    }
}

面向对象程序设计基本原则:

  • 封装。 封装原则声明所有重要信息都包含在对象中,并且只公开部门信息。每个对象的实现和状态都私有地保存在定义的类中。其他对象不能访问此类或无权进行更改。它们只能调用公共方法来访问。数据隐藏的这一特性提供了更高的程序安全性,并避免了意外的数据损坏。
  • 抽象。 对象只显示与使用其他对象相关的内部机制,隐藏任何不必要的实现代码。派生类可以扩展其功能。这个原则可以帮助开发人员随着时间的推移更容易地进行额外的更改或添加。
  • 继承。 类可以重用来自其他类的代码和属性。可以分配对象之间的关系和子类,使开发人员能够重用公共逻辑,同时仍然维护唯一的层次结构。继承是强关联的基类属性,减少开发时间,并确保更高级别的准确性。
  • 多态性。 对象被设计为共享行为,并且它们可以采用多种形式。程序确定每次从父类(接口)执行该对象时需要哪些含义或用法,从而减少重复代码的需要。多态性允许不同类型的对象通过相同的接口。

image

从java语言规范(JLS)来看

  • 声明为 private 的类的成员不会被该类的子类继承。
  • 只有声明受保护或公共的类的成员才能由声明该类的包以外的包中声明的子类继承。
  • 构造函数、静态初始值设定项和实例初始值设定项不是成员,因此不能继承。
  • 类的字段、方法、成员类和成员接口可以具有相同的名称,因为它们在不同的上下文中使用,并且由不同的查找过程消除歧义(6.5)。然而,作为一种风格,这是不被鼓励的。
    https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#d5e13645

PS

其实尽可能按照OOP的原则来实现编码,可读性、可扩展性、可维护性都会提高,就算是一个简单的POJO类,如今的IDE都支持直接生成get/set方法、构造方法等,非常方便,必要的时候直接生成即可。当然也不是说所有的POJO类,都必须生成get/set方法,加入判断等,而是依据实际情况来具体处理。

posted @ 2024-10-30 15:05  冰雪女娲  阅读(6)  评论(0编辑  收藏  举报