OOP 简介
Intoduction
面向对象编程(主要称为 OOP)是解决问题的编程范例。
OO(面向对象)程序的美妙之处在于,我们将程序视为一组彼此通信的对象,而不是遵循特定顺序的顺序脚本。
有很多支持 OOP 的编程语言,一些流行的是:
- Java 的
- C++
- C#
众所周知,Python 也支持 OOP,但它缺少一些属性。
OOP 术语
OOP 中最基本的术语是 class。
一个类基本上是一个对象,它有一个状态,它根据它的状态工作。
另一个重要的术语是 instance。
将类视为用于创建自身实例的模板。该类是一个模板,实例是具体的对象。
从类 A 创建的实例通常被称为“类型 A”,类似于 5 的类型是 int , abcd
的类型是字符串。
创建名为 insance1 的类型(类) ClassA 的实例的示例 :
Java 的
ClassA instance1 = new ClassA();
C++
ClassA instance1;
要么
ClassA *instance1 = new ClassA(); # On the heap
Python
instance1 = `ClassA()`
正如你在上面的示例中所看到的,在所有情况下都提到了类的名称,之后有空括号(C++除外,如果它们为空,则可以删除括号)。在这些括号中,我们可以将 arguments
传递给 constructor 我们的类。
构造函数是每次创建实例时调用的类的方法。它既可以参与也可以不论证。如果程序员没有为他们构建的类指定任何构造函数,那么将创建一个空构造函数(一个什么都不做的构造函数)。
在大多数语言中,构造函数被定义为一种方法,没有定义其返回类型和类的相同名称(在几个部分中的示例)。
创建名为 b1 的类型(类) ClassB 的实例的示例。 ClassB 的构造函数接受一个 int 类型的参数 :
Java 的
ClassA instance1 = new ClassA(5);
要么
int i = 5; ClassA instance1 = new ClassA(i);
C++
ClassA instance1(5);
Python
instance1 = `ClassA(5)`
如你所见,创建实例的过程与调用函数的过程非常相似。
功能与方法
功能和方法都非常相似,但在面向对象设计(OOD)中它们各自都有其自身的含义。
方法是对类的实例执行的操作。该方法本身通常使用实例的状态来操作。
同时,函数属于类而不属于特定实例。这意味着它不使用类的状态或实例中存储的任何数据。
从现在开始,我们将仅在 Java 中展示我们的示例,因为 OOP 在这种语言中非常清晰,但是相同的原则适用于任何其他 OOP 语言。
在 Java 中,函数 static 在其定义中包含单词,如下所示:
// File's name is ClassA
public static int add(int a, int b) {
return a + b;
}
这意味着你可以从脚本中的任何位置调用它。
// From the same file
System.out.println(add(3, 5));
// From another file in the same package (or after imported)
System.out.println(ClassA.add(3, 5));
当我们从另一个文件调用该函数时,我们使用它所属的类的名称(在 Java 中,这也是文件的名称),这给出了函数属于类而不是它的任何实例的直觉。
相比之下,我们可以像这样在 ClassA 中定义一个方法 :
// File's name is ClassA
public int subtract(int a, int b){
return a - b;
}
在这个 decleration 之后我们可以像这样调用这个方法:
ClassA a = new ClassA();
System.out.println(a.subtract(3, 5));
这里我们需要创建一个 ClassA 实例,以便调用它的方法减法。请注意,我们不能执行以下操作:
System.out.println(ClassA.subtract(3, 5));
这行会产生一个编译错误,抱怨我们在没有实例的情况下调用了这个非静态方法。
使用类的状态
让我们假设我们想再次实现我们的减法方法,但这次我们总是想减去相同的数字(对于每个实例)。我们可以创建以下类:
class ClassB {
private int sub_amount;
public ClassB(int sub_amount) {
this.sub_amount = sub_amount;
}
public int subtract(int a) {
return a - sub_amount;
}
public static void main(String[] args) {
ClassB b = new ClassB(5);
System.out.println(b.subtract(3)); // Ouput is -2
}
}
当我们运行这段代码,一个新的实例命名为 b 类的 ClassB 的创建它的构造被馈送值 5 。
构造函数现在接受给定的 sub_amount 并将其存储为自己的私有字段,也称为 sub_amount (此约定在 Java 中非常有名,以将参数命名为与字段相同)。
在那之后,我们打印到控制台调用该方法的结果减去上 b 与值 3 。
请注意,在减法的实现中,我们不像构造函数那样使用 this.
。
在 Java 中,只有在该范围内定义了具有相同名称的另一个变量时,才需要编写 this
。同样适用于 Python 的 self
。
因此,当我们在减法中使用 sub_amount 时,我们引用每个类不同的私有字段。
另一个强调的例子。
我们只需将上面代码中的 main 函数更改为:
ClassB b1 = new ClassB(1);
ClassB b2 = new ClassB(2);
System.out.println(b1.subtract(10)); // Output is 9
System.out.println(b2.subtract(10)); // Output is 8
我们可以看到, b1 和 b2 是独立的,每个都有自己的状态。
接口和继承
An interface 是一个契约,它定义了一个类将具有哪些方法,从而定义它的功能。接口没有实现,它只定义了需要完成的操作。
Java 中的一个例子是:
interface Printalbe {
public void print();
}
该 Printalbe 接口定义了一个方法调用的打印,但它并没有给其实施(很奇怪的 Java)。声明自己为 implementing
此接口的每个类都必须为 draw 方法提供一个实现。例如:
class Person implements Printalbe {
private String name;
public Person(String name) {
this.name = name;
}
public void print() {
System.out.println(name);
}
}
如果 Person 声明自己实现 Drawable 但没有提供打印实现,则会出现编译错误,程序无法编译。
继承是指向扩展另一个类的类的术语。例如,假设我们现在有一个有年龄的人。实现这样的人的一种方法是复制 Person 类并编写一个名为 AgedPerson 的新类,它具有相同的字段和方法,但它有另一个属性 -age。
这很糟糕,因为我们复制整个代码只是为了给我们的类添加一个简单的特性。
我们可以使用继承继承 Person ,从而获得它的所有功能,然后使用我们的新功能增强它们,如下所示:
class AgedPerson extends Person {
private int age;
public AgedPerson(String name, int age) {
super(name);
this.age = age;
}
public void print() {
System.out.println("Name: " + name + ", age:" + age);
}
}
有一些新的事情发生:
- 我们使用保存的单词
extends
来表示我们继承自 Person(以及它的实现到 Printable ,所以我们不需要再次声明implementing Printable
)。 - 我们使用保存词
super
来调用 Person 的构造函数。 - 我们用一个新的覆盖了 Person 的打印方法。 **
这是非常熟悉的 Java 技术,所以我不会深入研究这个主题。但是我会提到在开始使用它们之前应该学习很多关于继承和接口的极端情况。例如,哪些方法和函数是继承的?从类继承时,私有/公共/受保护字段会发生什么?等等。
抽象类
一个 abstract class 是 OOP 相当先进的术语,描述了两个接口和继承的组合。它允许你编写一个同时具有已实现和未实现的方法/函数的类。在 Java 中,这是通过使用关键字 abstract
来完成的,我不会更多地解释它是一个简单的例子:
abstract class AbstractIntStack {
abstract public void push(int element);
abstract public void pop();
abstract public int top();
final public void replaceTop(int element) {
pop();
push(element);
}
}
注意:final
关键字表示从此类继承时无法覆盖此方法。如果一个类被声明为 final,那么任何类都不能从它继承。