前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【JavaSE】面向对象三大特征——封装、继承、多态

【JavaSE】面向对象三大特征——封装、继承、多态

作者头像
鸡先生
发布2022-10-29 17:14:22
2390
发布2022-10-29 17:14:22
举报
文章被收录于专栏:鸡先生鸡先生

前言:本文主要介绍思想

封装

封装这一概念并不仅存在与面向对象中,甚至说封装这一概念不仅限于编程中,其实生活中的封装无处不在。比如

代码语言:javascript
复制
需求:你到银行取钱

参数:你只需要提供银行卡和密码

返回值:柜员会将现金取给你。

方法:至于柜员在柜台后面如何验证你的密码、余额,又是如何拿到现金给你,你都不知道也无需知道

———— 这就是封装

封装说白了就是隐藏细节

生活中的例子帮我们理解概念,我们再来看一下程序中的封装

代码语言:javascript
复制
Arrays.sort(arr);

你调用库中的某个方法,来实现某个功能,你只需要传入正确的参数,即可让方法运行,达到你想要的结果,至于方法内部进行了怎样的操作你不知道也无需知道

——这就是封装,方法封装了算法的细节

对于对象来说,访问权限其实也是封装的一种体现

代码语言:javascript
复制
public class User{
    private Long id;
    public Long getId(){
        return id;
    }
    public void setId(Long id){
        this.id=id;
    }
}

比如:

我们经常用private 修饰成员变量,然后选择性地提供public 方法以供外部访问成员变量。

在初学时可能会觉得多此一举,明明可以直接操作成员变量,为啥还要通过get、set方法来操作

举个例子

image
image

将phone字段封装起来,外部在调用set方法设置手机号时,便可以限制手机号的长度

然后调用get方法时,可以隐去关键信息,让手机号的中间四位数变成*号显示

这里就可以体现出封装的好处:

  • 它可以对成员进行更精准的控制
  • 让对象和调用者解耦类内部的结构和实现可以自由修改
  • 同时也能保证数据的安全性、有效性

继承

继承中有两个非常重要的概念,搞懂这两个概念也就能熟练掌握继承了

is-a

is-a,中文称为“是一种”,简单来说,如果你可以这样描述

代码语言:javascript
复制
Dog is a Animal? yes

Cat is a Animal? yes

Apple is a Animal? no

B 是一种 A,那么B 就继承了 A

能通过is-a 测试,在逻辑上和程序中就能顺利的使用继承关系

拿猫叫狗叫动物叫举例,比如现在Dog和Cat类继承了Animal类

image
image

然后来看一下这三段代码的编译结果,前两段代码通过了is-a 测试,自然是没有问题,而第三段代码就没有通过is-a测试,所以编译失败。编译器进行了语法检查,检查的方式是从=号右边往左读,右边是不是左边或者右边是不是左边的子类,如果是编译器就会通过,这三段代码不管是从逻辑上还是程序上都好理解。

代码语言:javascript
复制
Animal animal = new Dog();// 编译成功
Dog dog = animal; // 编译失败

animal 变量是用Animal 类来做声明的,自然而然编译器就将其认定为Aniaml类,尽管第二行指向的是Animal的子类Dog,但animal变量的身份现在是Animal类,无法通过is-a 测试导致编译失败

编译器会检查父子类之间的is-a 的关系。

如果你不想要让编译器啰嗦

代码语言:javascript
复制
Animal animal = new Dog();
Dog dog=(Dog)animal;

可以加上强制类型转换,让编译器住嘴,编译器就让这段代码通过了编译,不过后果得自行负责

子类之间不能使用强制类型转换

总结:使用is-a原则就可以判断何时编译成功,何时编译失败,并留意指向的对象类型,就可以判断何时类型转换成功,何时会抛出异常,然后在使用继承前,也可以在逻辑上判断是否该使用继承。

狗是动物嘛?是的,所以狗继承动物没有一点问题。

猫是动物嘛?是的,所以猫继承动物也没问题。

苹果是动物嘛?不是,这在逻辑上说不过去,程序中也不应该让苹果继承动物类,否则会导致程序的整体逻辑混乱

image
image

当然你完全可以让Apple extends Animal,但这样是完全不合理的

作为开发者应当避免这种乱继承的行为

has-a

has-a 中文意思是“有一个”,它可以用来判断,类与成员的关系,比如

代码语言:javascript
复制
public class Bird extends Animal{
    private String wings;
}

鸟是一种动物,那它可以继承Animal类这是is-a,鸟有翅膀,那Bird(鸟)类可以定义一个成员变量为翅膀,这个翅膀的成员变量就不适合定义在Animal类中也不适合定义在Dog和Cat类中

如果我们想设计出一个职责清晰的类,就需要好好的思考has-a关系。

总结:

  • is-a 帮助我们判断父子类的关系
  • has-a 帮助我们判断类与成员变量的关系

多态

三大特性的最后一个,也是最重要的一个。多态顾名思义:多种形态,听起来有点抽象。 假设你开了家宠物店,给宠物提供洗澡服务,于是你吩咐店员贴一个公告,咱们店可以让所有的哈士奇洗澡,结果都带着哈士奇来洗澡了。用代码表示就是宠物店提供了一个方法

代码语言:javascript
复制
// 参数是哈士奇,执行的功能是洗澡
public void shower(哈士奇 a);

但好像有点问题,有居民带了一只金毛过来

代码语言:javascript
复制
shower(哈士奇类的对象); //通过,因为转递进来的参数是匹配的
shower(金毛类的对象);   //报错,因为传递进来的参数不是哈士奇
shower(萨摩耶类的对象); //报错,因为传递进来的参数不是哈士奇
shower(猫类的对象);     //报错,因为传递进来的参数不是哈士奇

店员不允许金毛进店,因为你吩咐的是让哈士奇洗,金毛并不是哈士奇,所以不行,然后其他居民带着萨摩耶,柴犬啥的过来,店员都把他们拒之门外,你失去了很多顾客,但你还没办法生气,因为店员尽责的执行了你下达的命令。 这个店员就是编译器,语法不对的地方,它就让你编译失败。 你发现你好像做了一件傻事,明明自己开的是一个宠物店却只规定哈士奇过来洗澡,那生意自然少了很多。

你决定要让各种各样的宠物,哈士奇也行金毛也行

代码语言:javascript
复制
// 方法重载

public void shower(哈士奇,a); //参数是哈士奇
public void shower(哈士奇,a); //参数是金毛
public void shower(哈士奇,a); //参数是萨摩耶
...
...
...

因为宠物品种实在是太多了,你必须考虑各种各样的品种,于是你定义了一万个方法。 然而你发现,这样的成本实在是太高也太麻烦了,问题虽然能够解决,但也导致你筋疲力尽。 于是你决定改变,你重新吩咐店员只要是宠物(动物)过来,就可以享受洗澡服务

代码语言:javascript
复制
//参数是宠物(动物)类,执行的功能是洗澡
public void shower(Animal a);

这个指令一下达,生意立马就好了

代码语言:javascript
复制
shower(哈士奇类的对象); //通过,哈士奇类继承了Animal
shower(金毛类的对象);   //通过,金毛类继承了Animal
shower(萨摩耶类的对象); //通过,萨摩耶类继承了Animal
shower(猫类的对象);     //通过,猫类继承了Animal

因为别人不管是带哈士奇还是萨摩耶,甚至带猫过来都可以,用程序描述就是这段代码 这就是多态的一种体现,你只定义了一个方法和一个参数类型,但是传进来的参数却各种形态都有。 是不是非常方便,通过刚才的例子,我们可以总结一下多态的必要条件:

  1. 要有继承
  2. 父类引用指向子类对象
image
image

你参数定义的是Animal类,传进来的参数虽然可以多种多样,但是不能瞎传,得是Animal的子类才可以。 参数类型是父类,传进来的却是子类,这就上面继承说的is-a原则所体现出来的父类的引用指向子类的对象。 这些理解后我们再来深入一些

现在你的宠物点决定扩展业务了,不光要提供洗澡的服务,还有提供进食的服务。

代码语言:javascript
复制
// 宠物类
class Animal{
    //进食方法
    public void eat(){
        System.out.pritln("宠物吃东西啦~");
    }
}

// 你学聪明了,参数一开始设置的就是宠物类,这样所有宠物都可以进来
public void helpEat(Animal a){
    // 调用宠物本身的进食方式
    a.eat();    //打印的结果自然是"宠物吃东西啦~"
}

宠物进食自然是宠物的行为,在程序中行为就是方法,然后你照看宠物进食,并不是你进食,所以宠物来到宠物店后,那就要让宠物自己进食,即调用宠物的进食方法,这么一看好像特别完美。 但你立马又发现问题了,每个不同的宠物品种喜欢吃的东西不一样,吃的方式也不一样,可你现在调用的是单一的宠物进食方法,不管是什么宠物进来,打印的都是一句话,这就很尴尬了,因为狗是这种吃法,猫是另一种吃法,在你这就变成了同一种吃法。 要解决这个问题,就应该体现出,每个宠物吃法的特性。

代码语言:javascript
复制
class 哈士奇 extends Animal {
    @Override
    public void eat(){
        System.out.pringln("哈士奇开始吃狗粮啦~");
    }
}
class 狸花猫 extends Animal {
    @Override
    public void eat(){
        System.out.pringln("哈士奇开始吃猫粮啦~");
    }
}
class 东北虎 extends Animal {
    @Override
    public void eat(){
        System.out.pringln("东北虎开始吃肉啦~");
    }
}

自身的特性用程序语言描述就是方法的重写嘛,各品种的宠物重写了方法后

代码语言:javascript
复制
helpEat(哈士奇对象); // 传进来的参数类型是哈士奇对象,打印结果是:"哈士奇开始吃狗粮啦~"
helpEat(狸花猫);     // 传进来的参数类型是狸花猫对象,打印结果是:"狸花猫开始吃猫粮啦~"
helpEat(东北虎);     // 传进来的参数类型是东北虎对象,打印结果是:"东北虎开始吃肉啦~"

宠物店再进行服务的时候,效果就立马不一样了,这里就可以看出多态的好处,当父类的引用指向子类的对象时,调用的方法是子类重写后的方法,即体现了多种类型的传递,又体现了不同类型的特性,即复用了父类的属性和方法又扩展了自己的逻辑,所以继承所使用的关键字是 extends 而不是 inherit 更着重延申和扩展的意思,注意哦,这里的扩展完全不会影响原有逻辑,你宠物店的方法定义好后,无论宠物类的子类怎么扩展,对于当前功能来说,你都无需修改原代码,这也就是常说的开闭原则,对修改关闭,对扩展开放。

可以说多态就是面向对象的核心,它将扩展性达到了最好

到此面向对象三大特性就说完了。 在学习面向对象的时,语法没什么了不起的。 我们学习的应该是设计思路和理念,不是说用了对象就真的复用和扩展了。 如果不经思考滥用,不仅不能发挥面向对象的好处反而会让代码难以理解和难以维护。

image
image

代码设计是一门学问,也是一门艺术。 做出好的设计需要我们不断见识、不断思考、不断积累。 共勉!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 封装
  • 继承
    • is-a
      • has-a
      • 多态
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档