何为面向对象?

类:图纸

父类:可以理解为基础类,这里需要正确理解父类这个名词所包含的等级高的信息,父类中的属性和方法是一个基础
子类:从父类中衍生出来的类,具备父类中的基础,可以在父类上做扩展方法,也可以重写父类的方法和属性
注意:父类不代表比子类更丰富,子类拓展父类,这里不能通过等级层次去理解,更合理的方法是从进化的角度分析这两个概念

对象:根据图纸造出来的车

继承:

python中子类会继承父类的方法和属性,包括私有属性和私有方法,但通常不建议使用它们

封装:

主要利用私有属性和私有方法实现封装,好处就是修改这个类的内部方法而不影响使用这个类的代码(比如有更优的算法可以实现排序,那就可以直接替换封装好的类的排序方法而不用改动调用这个类的代码)

多态:

多态允许使用一个单一类型的实体(如变量,函数,接口)来代表多种类型的对象,简单来说,多态就是“一个接口,多种实现”。

代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Animal:
def speak(self):
pass

class Dog(Animal):
def speak(self):
return "Woof!"

class Cat(Animal):
def speak(self):
return "Meow!"

class Duck(Animal):
def speak(self):
return "Quack!"

def animal_sound(animal):
print(animal.speak())

# 创建不同的动物对象
dog = Dog()
cat = Cat()
duck = Duck()

# 使用相同的函数调用不同对象的方法
animal_sound(dog) # 输出: Woof!
animal_sound(cat) # 输出: Meow!
animal_sound(duck) # 输出: Quack!

# 多态性在集合中的应用
animals = [Dog(), Cat(), Duck()]
for animal in animals:
animal_sound(animal)

可以看到多态的几个关键特点:

方法重写: 每个子类(Dog, Cat, Duck)都重写了父类Animal的speak()方法。这允许每个子类提供自己的实现。
统一接口: 所有的动物类都有一个speak()方法,这提供了一个统一的接口。
运行时多态: animal_sound()函数接受一个Animal类型的参数,但它可以处理任何Animal的子类。在运行时,Python会调用对象实际类型的speak()方法。
灵活性: 我们可以轻松地添加新的动物类(如Cow),只要它实现了speak()方法,就可以无缝地与现有代码工作。

如何理解多态?

通过编写处理父类对象的代码,这些代码可以用于所有的子类对象,即通过父类就可以实现(批量/统一)管理不同的子类对象,这些子类对象继承父类有多种实现,差不多等于多态的子类对象。

如何理解对象和类class的关系?

图纸和图纸造出来的东西的关系,在看项目源代码时,看到class很容易就想是不是这个类里面的代码完成了对应的功能,从最终执行来说,确实是这样,但准确来说这不是面向对象编程(oop)的思想,按照oop来说是这个class构造的对象实现了相关的功能,oop中一切皆是对象,这里我还没有从传统的顺序执行的代码编程中跳出来。

设计类class的原则

单一职责原则(SRP):一个类应该只有一个引起变化的原因。这意味着一个类应该只负责一项功能或一组紧密相关的功能。
开放/关闭原则(OCP):类应该对扩展开放,对修改关闭。这意味着你应当能够扩展一个类的行为,而不需要修改其源代码。
里氏替换原则(LSP):派生类必须能够替换其基类。这意味着基类和派生类应该能够互相替换,而不影响程序的正确性。
接口隔离原则(ISP):不应该强迫客户依赖于它们不使用的接口。这意味着一个类不应该实现它不需要的方法。
依赖倒置原则(DIP):高层模块不应该依赖于低层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
封装:隐藏内部实现细节,只暴露必要的接口。这有助于减少系统各部分之间的依赖关系。
重用:尽可能重用代码。这可以通过创建可重用的类和模块来实现。
灵活性:设计时要考虑未来可能的变化,使类和模块易于修改和扩展。
简洁性:避免不必要的复杂性。类应该简单、直接,易于理解。
可测试性:类应该易于测试。这通常意味着它们应该具有清晰的职责,并且应该尽量减少依赖关系。

包装类(Wrapper Class)

子类和包装类的区别和联系
a. 继承关系
子类:继承自一个父类,子类拥有父类的属性和方法,并可以对其进行扩展或重写。
包装类:包装类通常是final类,不能被继承。这意味着你不能创建一个继承自包装类的子类。例如,Integer类是final的,不能被继承。
b. 扩展能力
子类:子类可以通过继承父类的属性和方法,并添加新的功能。通过多态,子类对象可以被看作父类对象使用。
包装类:包装类不能被继承,因此不能扩展。但是它们提供了基本类型的对象化功能和一些额外的实用方法。
c. 多态性
子类:子类对象可以被赋值给父类引用,体现了多态性。例如,Animal类的子类Dog可以被赋值给Animal类型的变量。
包装类:包装类不是基本类型的子类,所以不能将基本类型赋值给包装类变量,反之亦然。需要通过装箱和拆箱来转换。

总结

联系:包装类是基本类型的对象化表示,通过装箱和拆箱机制,使得基本类型可以作为对象使用。它们和基本类型密切相关,但并不继承自基本类型。
区别:包装类通常是final类,不能继承,不能扩展。而子类可以继承和扩展父类的功能,支持多态性。包装类主要用于在需要对象的地方表示基本类型,同时提供了额外的实用功能。

自定义类的包装类

自定义包装类可以为原始类提供额外的功能、接口或封装逻辑,同时保持对原始类的控制。比如,你可以为一个类提供额外的验证逻辑、线程安全功能,或者通过包装类实现某些设计模式。

示例:自定义类及其包装类
我们先定义一个简单的类,然后为它编写一个包装类。

原始类:Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public void setName(String name) {
this.name = name;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}

包装类:PersonWrapper
我们可以创建一个包装类 PersonWrapper,它包含一个 Person 对象,并提供额外的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class PersonWrapper {
private Person person;

public PersonWrapper(Person person) {
this.person = person;
}

public String getName() {
return person.getName();
}

public int getAge() {
return person.getAge();
}

// 提供额外的功能
public void printGreeting() {
System.out.println("Hello, " + person.getName() + "! You are " + person.getAge() + " years old.");
}

// 你还可以通过包装类对原始类的行为进行控制或修改
public void setName(String name) {
System.out.println("Setting name to: " + name);
person.setName(name);
}

public void setAge(int age) {
if (age > 0) {
person.setAge(age);
} else {
System.out.println("Invalid age!");
}
}

@Override
public String toString() {
return "PersonWrapper{person=" + person + "}";
}
}

使用包装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
PersonWrapper personWrapper = new PersonWrapper(person);

// 使用包装类调用原始类的方法
System.out.println("Name: " + personWrapper.getName());
System.out.println("Age: " + personWrapper.getAge());

// 使用包装类提供的额外功能
personWrapper.printGreeting();

// 通过包装类修改原始类的属性
personWrapper.setName("Bob");
personWrapper.setAge(25);
System.out.println(personWrapper);
}
}

包装类的应用场景
自定义包装类的用例很多,包括但不限于:

增强功能:为已有的类增加新的方法或属性,而不需要修改原始类。
代理模式:包装类可以在某些情况下代理对原始类的访问,常用于控制访问或延迟初始化。
装饰模式:通过包装类为原始类添加新功能,而不改变其结构。
适配器模式:包装类可以作为适配器,将一个类的接口转换为另一个类的接口。
总结
虽然在语言内置的包装类中,通常是为基本类型设计的,但你完全可以为自定义类创建包装类。这种设计可以帮助你扩展类的功能,增强代码的灵活性和可维护性。

class就是对多个对象进行如何交互的说明, new一个class的对象后就可以对多个对象进行交互,这些待交互的对象又是通过其它的class创建的,可以一直追溯到计算机的底层原理。(可以结合gcc新版本是用旧版本的gcc编译的)