✨作者:猫十二懿
❤️🔥账号:CSDN 、掘金 、个人博客 、Github
🎉公众号:猫十二懿
这里只是简单的将《大话设计模式【Java溢彩加强版】》的内容简单是复述一下,并加上自己的理解
在学习Java设计模式的时候先简单的回顾一下以前的面向对象的基础知识,因为这个关系到我们后面能不能更好的理解23种设计模式。
面向对象基础
面向对象编程,英文叫Object-Oriented Programming(以后简称OOP),其实就是针对对象来进行编程的意思。
1、 类与实列
一切事物皆为对象,即所有的东西都是对象,对象就是可以看到、感觉到、听到、触摸到、尝到或闻到的东西。
对象定义:对象是一个自包含的实体,用一组可识别的特性和行为来标识
类的定义:类就是具有相同的属性和功能的对象的抽象的集合
下面通过定义一个猫类说明:
/**
* @Author Shier
* CreateTime 2023/4/3 16:47
*/
public class Cat {
public String shout() {
return "喵";
}
}
定义类时注意事项:
- 类名称首字母记着要大写。多个单词则各个首字母大写
- 对外公开的方法需要用
public
修饰符。
对象的实例化:实例,就是一个真实的对象,实例化就是创建对象的过程,使用new关键字来创建。
我们在另外一个测试类里面进行实例化Cat
类
/**
* @author Shier
* CreateTime 2023/4/3 16:48
*/
public class OopTest {
public static void main(String[] args) {
// 创建一个Cat类实列
Cat cat = new Cat();
// 将实例化对象的方法进行调用,并输出到控制台
System.out.printf(cat.shout());
}
}
最后运行测试类就能看到输出一个“喵”。
如果我们想每次对猫类进行初始化的时候,想要给这个实例化对象赋予一个名字,那么就该使用构造方法
2、构造方法
构造方法定义:构造方法,又叫构造函数,其实就是对类进行初始化。构造方法与类同名,无返回值,也不需要void,在new的时候调用。
所有类都有构造方法,如果你不编码时没有给类自定义一个构造方法,则系统默认生成空的构造方法,若你有定义的构造方法,那么默认的构造方法就会失效了
所以说将Cat
类改造成如下的样子
/**
* @Author Shier
* CreateTime 2023/4/3 16:47
*/
public class Cat {
// 私有变量name
private String name = "";
// Cat的构造方法,携带一个name参数
public Cat(String name) {
this.name = name;
}
//
public String shout() {
return "我叫" + name + " 喵~~~";
}
}
此时测试类只要改成如下的样子,就能在实例化的时候进行赋予名字。
// 创建一个Cat类实列
Cat cat = new Cat("小九");
3、方法重载
方法重载定义:方法重载提供了创建同名的多个方法的能力,但这些方法需使用不同的参数类型。注意并不是只有构造方法可以重载,普通方法也是可以重载的。
注意方法重载注意点:
- 两个方法必须要方法名相同
- 参数类型或个数必须要有所不同
总得来说方法重载可在不改变原方法的基础上,新增功能。
所以说将猫类添加一个无参的构造方法:
// Cat的无参构造方法
public Cat() {
this.name = "xxx";
}
4、属性和修饰符
属性定义:属性是一个方法或一对方法,即属性适合于以字段的方式使用方法调用的场合(setXxx、getXxx)。
字段是存储类要满足其设计所需要的数据,字段是与类相关的变量
知道了属性那么我们再次对猫类进行改造,增加一个功能
/**
* 内部字段 shoutNum
*/
private int shoutNum = 5;
/**
* 其他类给shoutNum字段设置值
*
* @param shoutNum
*/
public void setShoutNum(int shoutNum) {
// 控制叫的次数
if (shoutNum <= 10) {
this.shoutNum = shoutNum;
} else {
this.shoutNum = 10;
}
}
/**
* 其他类能获取到shoutNum的值
*
* @return
*/
public int getShoutNum() {
return shoutNum;
}
public String shout() {
String result = "";
for (int i = 0; i < shoutNum; i++) {
result += "喵";
}
return "我叫" + name + result + "~~~";
}
最后在测试类当中进行复制
// 给猫进行控制叫声次数
cat.setShoutNum(5);
修饰符
-
public:该工程下的所有类都能访问到,对外开放的,使用对象:类、接口、变量、方法
-
protected:表示继承时子类可以对基类有完全访问权,使用对象:变量、方法。 注意:不能修饰类(外部类)。
用protected修饰的类成员,对子类公开,但不对其他类公开。所以子类继承于父类,则子类就拥有了父类的除private外的属性和功能
-
private:只允许同一个类中的成员访问,其他类包括它的子类无法访问,俗称私有的。使用对象:变量、方法。 注意:不能修饰类(外部类)
-
default: (即默认,什么也不写) 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
5、封装
封装定义:每个对象都包含它能进行操作所需要的所有信息,这个特性称为封装,因此对象不必依赖其他对象来完成自己的操作
封装的优势:
- 良好的封装能够减少耦合
- 类内部的实现可以自由地修改
- 类具有清晰的对外接口(属性和方法)
封装的好处很好理解,比如刚才举的例子。我们的房子就是一个类的实例,室内的装饰与摆设只能被室内的居住者欣赏与使用,如果没有四面墙的遮挡,室内的所有活动在外人面前一览无遗。由于有了封装,房屋内的所有摆设都可以随意地改变而不用影响他人。然而,如果没有门窗,一个包裹得严严实实的黑箱子,即使它的空间再宽阔,也没有实用价值。房屋的门窗,就是封装对象暴露在外的属性和方法,专门供人进出,以及流通空气、带来阳光。
6、继承
继承定义:对象的继承代表了一种is-a
的关系,如果两个对象A和B,可以描述为’B是A’,则表明B可以继承A
继承者还可以理解为是对被继承者的特殊化,因为它除了具备被继承者的特性外,还具备自己独有的个性。
例如,猫就可能拥有抓老鼠、爬树等’哺乳动物’对象所不具备的属性。因而在继承关系中,继承者可以完全替换被继承者,反之则不成立。
继承定义了类如何相互关联,共享特性。
继承的工作方式是,定义父类和子类,或叫作基类和派生类,其中子类继承父类的所有特性。子类不但继承了父类的所有特性,还可以定义新的特性。
继承(is-a的理解):
- 子类继承于父类
- 子类拥有父类非
private
的属性和功能 - 子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能
- 子类还可以以自己的方式实现父类的功能(方法重写)
现在定义一个Animal类,主要是将动物一些共有的特性放在一起,通过继承,然后子类在扩展属于自己的特性。
/**
* @Author Shier
* CreateTime 2023/4/4 16:47
*/
public class Animal {
protected String name = "";
public Animal(String name) {
this.name = name;
}
protected int shoutNum = 5;
public void setShoutNum(int shoutNum) {
if (shoutNum <= 10) {
this.shoutNum = shoutNum;
} else {
this.shoutNum = 10;
}
}
public int getShoutNum() {
return shoutNum;
}
public String shout() {
return "";
}
}
然后通过继承,子类从它的父类中继承的成员有方法、属性等,但对于构造方法,有一些特殊,它不能被继承,只能被调用。对于调用父类的成员,可以用base关键字。
Dog
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
public Dog() {
super();
}
public String shout() {
String result = "";
for (int i = 0; i < shoutNum; i++) {
result += "汪";
}
return "我叫" + name + result + "~~~";
}
}
Cat
public class Cat extends Animal{
public Cat() {
super();
}
public Cat(String name){
super(name);
}
public String shout() {
String result = "";
for (int i = 0; i < shoutNum; i++) {
result += "喵";
}
return "我叫" + name + result + "~~~";
}
}
不用继承的话,如果要修改功能,就必须在所有重复的方法中修改,代码越多,出错的可能就越大。
继承的优点
- 继承使得所有子类公共的部分都放在了父类,使得代码得到了共享,避免了重复
- 继承可使得修改或扩展继承而来的实现都较为容易
继承的缺点:
-
父类变,则子类不得不变
-
继承会破坏包装,父类实现细节暴露给子类
-
增加类之间的耦合性。
耦合性:两个类尽管分开,但如果关系密切,一方的变化都会影响到另一方,这就是耦合性高的表现,继承显然是一种类与类之间强耦合的关系。
如下如所示就是继承的优点:(只需修改父类,子类也可以改变)也可以说是缺点。
何时使用继承?
当两个类之间具备’is-a’的关系时,就可以考虑用继承了,因为这表示一个类是另一个类的特殊种类,而当两个类之间是’has-a’的关系时,表示某个角色具有某一项责任,此时不适合用继承。
比如人有两只手,手不能继承人;再比如飞机场有飞机,飞机也不能去继承飞机场。
7、多态
多态定义:多态表示不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行。
同样是鸟,同样展开翅膀的动作,但老鹰、鸵鸟和企鹅之间,是完全不同的作用。老鹰展开翅膀用来更高更远地飞翔,鸵鸟用来更快更稳地奔跑,而企鹅则是更急更流畅地游泳
多态注意点:
- 子类以父类的身份出现
- 子类在工作时以自己的方式来实现
- 子类以父类的身份出现时,子类特有的属性和方法不可以使用
方法重写
方法重写定义:子类可以选择使用override关键字,将父类实现替换为它自己的实现,这就是方法重写Override,或者叫作方法覆写。
我们上面已经在Animal中写了一个shout方法,下面将测试类,进行修改
// 创建一个Animal动物数组
Animal[] arrayAnimals = new Animal[5];
arrayAnimals[0] = new Cat("小秀");
arrayAnimals[1] = new Dog("花花");
arrayAnimals[2] = new Cat("丫丫");
arrayAnimals[3] = new Dog("阿明");
arrayAnimals[4] = new Cat("一一");
for (int i = 0; i < arrayAnimals.length; i++) {
System.out.println(arrayAnimals[i].shout());
}
其实这就是多态的写法了,左边是父类,右边是子类
不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行。
多态的原理:当方法被调用时,无论对象是否被转换为其父类,都只有位于对象继承链最末端的方法实现会被调用。也就是说,虚方法是按照其运行时类型而非编译时类型进行动态绑定调用的。
总得来说多态在编译和运行时:成员变量、静态方法看左边,非静态方法编译看左边,运行看右边
到这里可能还是不太懂多态的好处,等学了设计模式就知道了,没有学过设计模式,那么对多态乃至对面向对象的理解多半都是肤浅和片面的。
8、 重构
此时有了很多的类,比如牛马羊猪等动物,如果你只是单纯的复制粘贴(CV)前面的Cat类来的到新的动物类,那么你会发现这样会有很多的重复代码。
此时我因该在Animal中新增一个叫声的方法,并且将具体动物类(比如Cat类中)的Shout方法迁移至Animal类中。
public String shout() {
String result = "";
for (int i = 0; i < shoutNum; i++) {
result = getShoutSound() + " ";
}
return "我叫" + name + result + "~~~";
}
protected String getShoutSound() {
return "";
}
这样子子类看起来就很简单了。
而且你看的idea2022.2.1中已经提示你有四个类重写了你的方法
9、抽象类
Java允许把类和方法声明为abstract,即抽象类和抽象方法。
可以考虑把实例化没有任何意义的父类,改成抽象类,同样地,对于Animal类的getShoutSound方法,其实方法体没有任何意义,所以可以将修饰符改为abstract,使之成为抽象方法。
抽象类的注意点:
- 抽象类不能实例化
- 抽象方法是必须被子类重写的方法
- 如果类中包含抽象方法,那么类就必须定义为抽象类,不论是否还包含其他一般方法。
- 抽象类只能继承(我们更多是使用接口,接口可以多实现,而Java的单一继承的,不利于扩展)
抽象类拥有尽可能多的共同代码,拥有尽可能少的数据
何时使用抽象类:
抽象类通常代表一个抽象概念,它提供一个继承的出发点,当设计一个新的抽象类时,一定是用来继承的,所以,在一个以继承关系形成的等级结构里面,树叶节点应当是具体类,而树枝节点均应当是抽象类
也就是说,具体类不是用来继承的。我们作为编程设计者,应该要努力做到这一点。比如,若猫、狗、牛、羊是最后一级,那么它们就是具体类,但如果还有更下面一级的金丝猫继承于猫、哈巴狗继承于狗,就需要考虑把猫和狗改成抽象类了,当然这也是需要具体情况具体分析的。
由于抽象类使用的不多,下面再来讲讲接口
10 、接口
接口是把隐式公共方法和属性组合起来,以封装特定功能的一个集合。一旦类实现了接口,类就可以支持接口所指定的所有属性和成员。声明接口在语法上与声明抽象类完全相同,但不允许提供接口中任何成员的执行方式。
接口注意点:
- 接口不能实例化
- 不能有构造方法和字段
- 不能有修饰符,比如public、private等
- 不能声明为虚拟的或静态的等
- 实现接口的类就必须要实现接口中的所有方法和属性。
接口优点:
- 一个类可以支持(实现)多个接口,多个类也可以支持相同的接口(多个类能同时实现同一个接口)
下面我们再对上面的 动物 进行改造,首先创建一个接口,
接口用interface声明,而不是class(接口名称前加’I’会更容易识别),接口中的方法或属性前面不能有修饰符、方法没有方法体。
/**
* @author Shier
* CreateTime 2023/4/4 16:46
*/
public interface ChangeThings {
/**
* 接口里面的方法
* 这里的修饰符可以省略
*/
public String changThings(String thing);
}
创建一个机器猫类
/**
* @Author Shier
* CreateTime 2023/4/3 16:47
*/
public class MachineCat extends Cat implements ChangeThings {
public MachineCat() {
super();
}
public MachineCat(String name) {
super(name);
}
@Override
public String changThings(String thing) {
return super.shout() + "我是一机械猫,我会很多事情,如变出" + thing;
}
}
创建一机械狗类:
public class MachineDog extends Cat implements ChangeThings {
public MachineDog() {
super();
}
public MachineDog(String name) {
super(name);
}
@Override
public String changThings(String thing) {
return super.shout() + "我是一机械狗,我会很多事情,我比普通的狗更加智能" + thing;
}
}
测试类如下:
// 创建两个实列
MachineCat machineCat = new MachineCat("叮当猫");
MachineDog machineDog = new MachineDog("细狗");
// 接口数组
ChangeThings[] changeThings = new ChangeThings[2];
changeThings[0] = machineCat;
changeThings[1] = machineDog;
// 多态
System.out.println(changeThings[0].changThings("很多东西!"));
System.out.println(changeThings[1].changThings("给主人买菜!"));
我们定义了一个接口,让每个类都去实现这个接口,就必须实现这个接口的方法,当不同类的实例对象去调用自己类当中实现接口的那个方法,这样就能实现不同的类,执行不同的功能。
抽象类与接口的不同
从表象上区分:
抽象类可以给出一些成员的实现,接口却不包含成员的实现,抽象类的抽象成员可被子类部分实现,接口的成员需要实现类完全实现,一个类只能继承一个抽象类,但可实现多个接口等。
从形态上去区分:
-
类是对对象的抽象,抽象类是对类的抽象,接口是对行为的抽象
接口是对类的局部(行为)进行的抽象,而抽象类是对类整体(字段、属性、方法)的抽象。如果只关注行为抽象,那么也可以认为接口就是抽象类。总之,不论是接口、抽象类、类甚至对象,都是在不同层次、不同角度进行抽象的结果,它们的共性就是抽象。
-
如果行为跨越不同类的对象,可使用接口;对于一些相似的类对象,用继承抽象类
比如猫呀狗呀它们其实都是动物,它们之间有很多相似的地方,所以我们应该让它们去继承动物这个抽象类,而飞机、麻雀、超人是完全不相关的类,叮[插图]是动漫角色,孙悟空是古代神话人物,这也是不相关的类,但它们又是有共同点的,前三个都会飞,而后两个都会变出东西,所以此时让它们去实现相同的接口来达到我们的设计目的就很合适了。
-
从设计角度讲,抽象类是从子类中发现了公共的东西,泛化出父类,然后子类继承父类,而接口是根本不知子类的存在,方法如何实现还不确认,预先定义。
从前面的实现过程来看,先是有一个Cat类,然后再有一个Dog类,观察后发现它们有类似之处,于是泛化出Animal类,这也体现了敏捷开发的思想,通过重构改善既有代码的设计
11、集合
首先看看数组的优缺点
数组的优点:比如说数组在内存中连续存储,因此可以快速而容易地从头到尾遍历元素,可以快速修改元素等。
缺点:应该是创建时必须要指定数组变量的大小,还有在两个元素之间添加元素也比较困难。
数组长度设置过大,造成内存空间浪费;长度设置过小造成溢出
Java提供了用于数据存储和检索的专用类,这些类统称集合。这些类提供对堆栈、队列、列表和哈希表的支持。大多数集合类实现相同的接口。
ArrayList是程序包java.util.ArrayList下的一部分,它是使用大小可按需动态增加的数组实现Collection接口。
ArrayList的容量是ArrayList可以保存的元素数。ArrayList的默认初始容量为0。随着元素添加到ArrayList中,容量会根据需要通过重新分配自动增加。使用整数索引可以访问此集合中的元素。此集合中的索引从零开始。
数组的容量是固定的,而ArrayList的容量可根据需要自动扩充。ArrayList提供添加、插入或移除某一范围元素的方法。
对之前的测试类进行改造:
如果推出则将他们移除,但是每次移除就会重新的将索引调整,后面的元素会向前移动,所以说每次删除都是remove(1)
集合ArrayList相比数组的好处
- 使用大小按需动态增加,不用受事先设置其大小的控制
- 可以随意地添加、插入或移除某一范围元素
但是ArrayList不是类型安全的。
ArrayList就意味着都需要将值类型装箱为Object对象,使用集合元素时,还需要执行拆箱操作,这就带来了很大的性能损耗。
装箱与拆箱:
- 装箱就是把值类型打包到Object引用类型的一个实例中。
- 拆箱就是指从对象中提取值类型。
相对于简单的赋值而言,装箱和拆箱过程需要进行大量的计算。对值类型进行装箱时,必须分配并构造一个全新的对象。其次,拆箱所需的强制转换也需要进行大量的计算。
我们为了避免这些装箱和拆箱过程消耗的资源,我们使用泛型来解决
12、泛型
泛型定义:泛型是具有占位符(类型参数)的类、结构、接口和方法,这些占位符是类、结构、接口和方法所存储或使用的一个或多个类型的占位符。泛型集合类可以将类型参数用作它所存储的对象的类型的占位符;类型参数作为其字段的类型和其方法的参数类型出现。
起始就是在集合后面添加一个 其中 T就是具体的数据类型
其实ArrayList和ArrayList在功能上是一样的,不同点就在于,它在声明和实例化时都需要指定其内部项的数据或对象类型,这就避免了刚才讲的类型安全问题和装箱拆箱的性能问题
通常情况下,都建议使用泛型集合,因为这样可以获得类型安全的直接优点而不需要从基集合类型派生并实现类型特定的成员。此外,如果集合元素为值类型,泛型集合类型的性能通常优于对应的非泛型集合类型(并优于从非泛型基集合类型派生的类型),因为使用泛型时不必对元素进行装箱。
学无止境,你需要不断地练习实践才可能真正成为优秀的开发工程师。🎊🎊🎊🎊🎊🎊🎊🎊🎊
742982)]
相对于简单的赋值而言,装箱和拆箱过程需要进行大量的计算。对值类型进行装箱时,必须分配并构造一个全新的对象。其次,拆箱所需的强制转换也需要进行大量的计算。
我们为了避免这些装箱和拆箱过程消耗的资源,我们使用泛型来解决
12、泛型
泛型定义:泛型是具有占位符(类型参数)的类、结构、接口和方法,这些占位符是类、结构、接口和方法所存储或使用的一个或多个类型的占位符。泛型集合类可以将类型参数用作它所存储的对象的类型的占位符;类型参数作为其字段的类型和其方法的参数类型出现。
起始就是在集合后面添加一个 其中 T就是具体的数据类型
[外链图片转存中…(img-o1nJiXFI-1684746742984)]
其实ArrayList和ArrayList在功能上是一样的,不同点就在于,它在声明和实例化时都需要指定其内部项的数据或对象类型,这就避免了刚才讲的类型安全问题和装箱拆箱的性能问题
通常情况下,都建议使用泛型集合,因为这样可以获得类型安全的直接优点而不需要从基集合类型派生并实现类型特定的成员。此外,如果集合元素为值类型,泛型集合类型的性能通常优于对应的非泛型集合类型(并优于从非泛型基集合类型派生的类型),因为使用泛型时不必对元素进行装箱。
学无止境,你需要不断地练习实践才可能真正成为优秀的开发工程师。🎊🎊🎊🎊🎊🎊🎊🎊🎊