面向过程与面向对象
什么是面向过程
一种自上而下的编程模式,以过程为核心的编程思想;把问题分解成多个步骤,每个步骤用函数去实现它,再以依次执行即可。
什么是面向对象
面向对象不再像面向过程一样去关心解决问题的步骤和顺序,而是把这些解决问题的步骤进行抽象,形成对象,然后通过对象之间调用、组合来解决问题;
也就是说,再进行面向对象编程时,我们把解决问题的属性和行为抽取出来封装成对象,然后再基于这些对象的属性和行为来解决问题。
例如:我们要造一道门,就要先把门的属性和它的功能(行为)定义出来,然后抽象成一个Door类;
举例说明
举个例子来区分一下面向过程和面向对象。
你一个程序员从别人那里接写代码的活。刚开始一个人,啪啦啪啦就开始写,写后端,写前端,然后测试,改bug,又测试;最后没问题然后交给客户;
后来,你有钱了开了个公司,招了些人,不用再自己写代码了,接到了活,直接开会,然后把任务交给你的产品经理和项目经理,最后他们完成了再交给客户;
在你一个人的时候就是面向过程,你必须知道代码怎么写,你要一步一步的去分析去完成,而当你开了公司就相当于面向对象,你不用再去管代码怎么写的,这样你与代码的关联度就降低了,降低了耦合性;
假如你接的新的活需要IOS,如过你一个人,不会就只能干瞪眼,因为客户不会等你,而你的公司的话,直接招一个IOS的程序员就行了,这就说明公司也就是面向对象更容易扩展,更易维护;
优劣对比
面向过程:面向过程性能比面向对象高。因为类调用时需要实例化,开销比较大,比较消耗资源。所以当极致追求性能时,一般采用面向过程。但是,面向过程没有面向对象易维护、易复用、易扩展。
面向对象:面向对象易维护、易复用、易扩展。因为面向对象有封装、继承、多态的特征,可以设计出低耦合的系统,使系统更加灵活、更易于维护。但是性能比面向过程更低。
面向对象三大基本特征
封装
封装是面向对象编程的核心思想。将对象的属性和行为封装起来,对外限制其访问权限,只对其可信的开放,对不可信的访问进行限制;我们可以用访问权限修饰符控制不同的访问对象。
继承
继承它可以使现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通继承创建的新类为“子类”,被继承的类为“父类”;
要实现继承,可以通过“继承”和“组合”来实现;继承概念的实现方式有两种:实现继承和接口继承。
实现继承是指直接适应基类的属性和方法而不用编写额外的代码的能力;
接口继承是指仅使用属性和方法名称,但是子类必须提供实现的能力;
在使用继承时,我们需要去判断清楚两个类之间关系应该时属于is-a还是has-a;例如门作为父类下,可以有铁门,木门,玻璃门等子类,因为他们有门的共性。但是门把手、门锁,就不能去继承门这个类,它们和门之间的关系因该是has-a,因为它们并不是一个门。
多态
所谓多态就是指一个类实例的相同方法在不同情形下有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(哪些操作)可以通过相同的方式予以调用。
简单的来说:多态就是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
按照这个概念定义,那么多态应该是一种运行期的状态。
多态的三个必要条件
1.必须有继承关系
2.子类必须覆写父类方法
3.父类引用指向子类对象
简单用代码解释下:
public class Person {
public void say() {
System.out.print("我是人");
}
}
//1.存在继承关系
public class Employee extends Person {
//2.子类覆写父类方法
public void say() {
System.out.print("我是普通职员");
}
}
//1.存在继承关系
public class Manager extends Person {
//2.子类覆写父类方法
public void say() {
System.out.print("我是经理");
}
}
public class Test {
public static void main(String[] args) {
//3.父类引用指向子类对象
Person p1 = new Employee();
Person p2 = new Manager();
p1.say();//输出 我是普通职员
p2.say();//输出 我是经理
}
}
这样就实现了多态,都是Person类的实例, p1.say调用的是Employee类的实现,而p2.say调用的是Manager的实现。
面向对象五大基本原则
单一职责原则SRP(Single-Responsibility Principle)
一个类应该只有一个引起它变化的理由,这说明一个类就应该只有一个工作;
通常意义下的单一职责,就指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。例如上面的Person类里,将Employee类、和Manager类都放在Person里设计,这样就会非常的混乱。在这个假设下,Person类每个方法都要用选择结构去判断它到底是那种情况,这样Person类就会显得十分臃肿。
开放封闭原则(Open-Closed principle)
软件实体对扩展进行开放,对修改封闭;
开放封闭原则主要体现在两个方面1.对扩展开放,当有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。2.对修改封闭,意味着类一旦设计完成,就可以独立完成工作,而不要对其进行任何尝试的修改。实现开放封闭原则的核心就是,对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改时封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以就是开放的。这不是说软件设计就不能做修改,我们要对软件的扩展要有预判,预先判断它以后可能会根据需求的变动而扩展,而不是随意去对每个地方多进行修改扩展。
Liskov替换原则(Liskov-Substitution Principle)
子类必须能够替换其父类;
只有子类能够替换父类时,才能保证系统在运行期间识别子类;这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序不会发生任何变化。同时,这以约束反过来是不成立的,子类可以替换父类,但父类不一定能替换子类。
依赖倒置原则(Dependecy-Inversion Principle)
上层模块不依赖于下层模块,它们都因该依赖于接口;抽象不依赖于具体,具体应该依赖于抽象;
例如,我们现在要去实现一个卖门的店(DoorStore),这个DoorStore里面就应该有很多具体的Door,如木门、铁门、玻璃门、塑料门等等;这个DoorStore就是上层模块,而具体的Door就是下层模块;这个DoorStore依赖于所有不同种类的Door,任何Door的改变都会影响到这个DoorStore。我们说DoorStore依赖于Door的实现。我们不想让DoorStore依赖于Door的具体,否则每多出不同种类的Door,DoorStore都要去依赖它;现在我们不从上层模块DoorStore思考,而是从下层的Door开始,我们可以把这些Door抽象出来,它们可以共享一个Door接口,现在这个DoorStore不在依赖于具体的Door类而是去依赖Door的接口,而哪些具体的Door类也依赖于这个Door接口;这就形成了依赖关系的倒置,上层的DoorStore不再去依赖下层的Door实现,而都去依赖了抽象。
接口隔离原则(Interface-Segregation Principle)
客户端不应该依赖于它不需要的接口;类间的依赖关系应该建立在最小的接口上;具体的说就是不要把一大堆方法塞进一个接口,导致这和个接口变得臃肿。应该根据实际需要,让接口中只有用的上的方法,也就是细化我们的接口;
接口隔离的好处
避免接口污染
一个类如果要实现一个接口,那么就要实现接口的所有方法,如果这个接口里包含这个类不需要的方法,那么就会造成接口污染,这是种不好的设计,会对系统留下隐患。
提高灵活性
一个类可以实现多个接口,我们要尽量的把臃肿的接口分成多个小的接口,通过这些小的接口的不同组合可以满足更多的需求。
提供定制服务
所谓的定制服务,就是通过细化接口,实现给不同的客户提供不同的接口的目的。定制服务可以有效避免因为给客户提供多余的方法而造成的危险。
实现高内聚
高内聚就是提高接口、类、模块的处理能力,减少对外交互。举个例子说就是你交给你手下一个任务一天之内完成任务,你只用第二天检查任务完成没有,并不用去关心它怎么完成这个任务的。这种不用关心细节,就等任务完成的的行为就是高内聚的表现。
具体到接口中,我们还是要细化我们的接口。接口是对外界的承诺,承诺的越少承担的风险也就越低。