写在前面:
其实这个系列的文章更加偏向于总结向,是对以往一些知识做个系统的总结,也是为已经有一定基础的人铺路,提供一个系统的思考。
对新人可能是很不友好的,可能有些人是刚接触,上来就是组合代理的,建议新人上网先做个学习,再把我这个对比记忆,说不定还能发现我的错误呢,说不准哦,三人行,必有我师;如果想和我讨论的,可以直接留言,也可以和我私信,我会很乐意和大家来讨论的。
同时也请大佬们不吝赐教!
面向对象具体的讲解就不赘述了,只简单地列举一下面向过程和面向对象的表达方式,一下就能看懂:
面向过程 | 面向对象 |
---|---|
是一种以过程为中心的编程思想,实现功能的每一步,都是自己实现的 | 是一种以对象为中心的编程思想,通过指挥对象实现具体的功能 |
如果想看看最基础的相对于面向对象的知识,随便搜搜就好啦,我这只是相当于一个总结。
如果面试官问你:面向对象的三大特征与五大原则是什么?你如果回答“封装、继承、多态!单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口隔离原则”,我建议你面试的时候戴个面具,防止被面试官啐一脸口水 ,人家问你是什么,你不能实诚地单单回答是什么,还应该做一个具体的描述,最起码让面试官知道“嗯,这小子懂”,是吧?哈哈
就一个类而言,应该仅有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。
对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。
任何基类可以出现的地方,子类一定可以出现。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。使用多个专门的接口比使用单个接口要好的多
主要列举一下怎么去复用代码,以方便做出对比,这种对比并不是要在这些方法中比较出来孰优孰劣,而是要根据他们的优缺点、特点,有目的性地配合使用,这才是关键。
对于两个类的复用,我尽量用两个类分开去写,一是比较规范,二是对稍微有些基础的人比较善意。
通过关键字extends来创建新的类来复用基类的非私有(private)的属性和方法:
package com.summary.inheritance520;
/**
* com.summary.inheritance520
*
* @author g55zhw
* @create 2020-09-01-08-50
*/
public class Father {
public void introduce(){
System.out.println("Father is coming~~");
}
}
package com.summary.inheritance520;
/**
* com.summary.inheritance520
*
* @author g55zhw
* @create 2020-09-01-08-51
*/
public class Son extends Father {
public void show(){
System.out.println("son is here...");
}
}
package com.summary.inheritance520;
import org.junit.Test;
/**
* com.summary.inheritance520
*
* @author g55zhw
* @create 2020-09-01-08-52
*/
public class DemoTest {
@Test
public void showInheritance() {
Son son = new Son();
System.out.print("基类方法:");
son.introduce();
System.out.print("子类方法:");
son.show();
}
}
基类方法:Father is coming~~
子类方法:son is here...
通过extends来实现继承关系,子类继承基类,创建子类对象,可以调用所有基类的非私有方法。一般应用于对于几个重复功能的类的统一抽取,来简化代码,同时提高基类代码的复用性。
特别注意:
package com.summary.interface520;
/**
* com.summary.interface520
*
* @author g55zhw
* @create 2020-09-01-09-16
*/
public interface Inter {
int NUM = 10;
/**
* 接口中的show方法
*/
void show();
}
package com.summary.interface520;
/**
* com.summary.interface520
*
* @author g55zhw
* @create 2020-09-01-09-17
*/
class InterImpl implements Inter{
public void method(){
System.out.println("NUM:" + NUM + "是接口中定义的成员变量,只能为常量");
}
/**
* 接口中的show方法
*/
@Override
public void show() {
System.out.println("我是重写之后的实现类");
}
}
package com.summary.interface520;
import org.junit.Test;
/**
* com.summary.interface520
*
* @author g55zhw
* @create 2020-09-01-09-17
*/
public class DemoTest {
@Test
public void showInterface() {
InterImpl demo = new InterImpl();
demo.method();
demo.show();
}
}
NUM:10是接口中定义的成员变量,只能为常量
我是重写之后的实现类
通过implements来实现实现关系(实现关系是一种关系,第一个实现是动词),实现类实现了接口(非抽象类的实现类必须重写所有方法),创建实现类对象,对象可以调用所有接口的非私有方法(注意是可以)。一般应用于对代码的规范使用,通过实现多个接口,来解决单一继承的不足(单继承无法提供多种方法——这里指的多种方法是不同功能的方法,因为我们一般一个类只提供一种方法,比如一个飞机,需要“起飞”、“转向”、“降落”等多种功能,这些功能一般写在不同的类里,而不是一个,这是变成的规范),同时也提高代码的复用性。
特别注意:
下面这段代码模仿组合的形式,写了这么几个类:Airframe机身类、Empennage尾翼类、Wing机翼类(组合了Engine引擎类)然后Plane飞机类将他们全都组合在一起(以上的类也可以继承,也可以实现接口,无所谓的,重点是飞机类将他们组合在了一起)
package com.summary.composition520;
/**
* com.summary.composition520
*
* @author g55zhw
* @create 2020-09-01-10-29
*/
public class Airframe {
public void function(){
System.out.println("我是Airframe,我的功能是载客");
}
}
package com.summary.composition520;
/**
* com.summary.composition520
*
* @author g55zhw
* @create 2020-09-01-10-29
*/
public class Empennage {
public void function(){
System.out.println("我是Empennage,我的功能是转向");
}
}
package com.summary.composition520;
/**
* com.summary.composition520
*
* @author g55zhw
* @create 2020-09-01-10-31
*/
public class Engine {
public void function(){
System.out.println("我是Engine,我的功能是提供动力");
}
}
package com.summary.composition520;
/**
* com.summary.composition520
*
* @author g55zhw
* @create 2020-09-01-10-28
*/
public class Wing {
Engine engine = new Engine();
public void function(){
engine.function();
System.out.println("我是Wing,我的功能是滑翔和调整高度;而且我已经装载了Engine");
}
}
package com.summary.composition520;
/**
* com.summary.composition520
*
* @author g55zhw
* @create 2020-09-01-10-34
*/
public class Plane {
Airframe airframe = new Airframe();
Empennage empennage = new Empennage();
Wing wing = new Wing();
void function(){
System.out.println("Plane已装载Airframe");
empennage.function();
System.out.println("Plane已装载Empennage");
wing.function();
System.out.println("Plane已装载Wing");
System.out.println("Plane已就绪,请求出库");
}
}
package com.summary.composition520;
import org.junit.Test;
/**
* com.summary.composition520
*
* @author g55zhw
* @create 2020-09-01-10-37
*/
public class DemoTest {
@Test
public void showComposition() {
Plane plane = new Plane();
plane.function();
}
}
Plane已装载Airframe
我是Empennage,我的功能是转向
Plane已装载Empennage
我是Engine,我的功能是提供动力
我是Wing,我的功能是滑翔和调整高度;而且我已经装载了Engine
Plane已装载Wing
Plane已就绪,请求出库
组合是一个很灵活的方式,他可以容纳很多对象,这些对象所依赖的类又可以随意去继承基类、实现接口,最终构成了一个复杂的对象,而不用破坏其中任何一个方法,而且每个类只专注于一项任务,没有破化开闭原则;也就是说其他类依旧不影响被另外的类使用,比如汽车要来继承引擎类,不需要重写整个引擎类,只需要继承引擎,然后定义自己的特有方法就可以了。
特别注意:
这里并非着重讲述代理,而静态代理有太多的局限性,所以只采用JDK动态代理的方法来说明问题。
package com.summary.proxy520;
/**
* com.summary.proxy520
*
* @author g55zhw
* @create 2020-09-01-11-15
*/
public interface Work {
/**
* work
*/
void work();
}
package com.summary.proxy520;
/**
* com.summary.proxy520
*
* @author g55zhw
* @create 2020-09-01-11-16
*/
public class WorkImpl implements Work {
/**
* work
*/
@Override
public void work() {
System.out.println("我要干活了");
}
}
package com.summary.proxy520;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* com.summary.proxy520
*
* @author g55zhw
* @create 2020-09-01-11-19
*/
public class ProxyHandler implements InvocationHandler {
private Object object;
public Object setObject(Object object) {
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我先喝口水");
Object invoke = method.invoke(this.object, args);
System.out.println("做完了,休息休息");
return invoke;
}
}
package com.summary.proxy520;
import org.junit.Test;
/**
* com.summary.proxy520
*
* @author g55zhw
* @create 2020-09-01-11-28
*/
public class ProxyTest {
@Test
public void showProxy() {
WorkImpl work = new WorkImpl();
ProxyHandler proxyHandler = new ProxyHandler();
Work workOne = (Work) proxyHandler.setObject(work);
workOne.work();
System.out.println("===============");
Work workTwo = (Work) proxyHandler.setObject(work);
workTwo.work();
}
}
我先喝口水
我要干活了
做完了,休息休息
===============
我先喝口水
我要干活了
做完了,休息休息
JDK代理,很容易看得出来,首先,调用者和被调用者实现了分离,降低了耦合,其次就是在保持开闭原则的前提下,增加而新的方法,也可以调用多个方法,我没有写第二个方法,所以连续调用了两次Work,大家应该可以理解本质的意思的。
特别注意:
这里不再将封装拿进来进行说明,因为封装思想是通过构建一个类来进行属性的集成,而其他的方法是类(或者接口)与类(或者接口)之间的关系。
好处 | 弊端 | |
---|---|---|
继承 | ①提高了代码的复用性 ②提高了代码的维护性(相对于没有面向对象来说) |
①类的耦合性增强了 ②只能单继承(接口与接口可以多继承), 但是可以多层继承 ③代码的维护性较差(相对于其他面向对象的方式) |
实现 | ①可以单实现,也可以多实现 ②降低程序的耦合性 ③对外提供规则 |
不能用于实例化对象(new一个对象) |
组合 | ①整体类与局部类之间松耦合,相互独立 ②支持动态扩展(扩展性优于继承) ③相较于实现,可以实例化对象 |
①创建整体类对象时,需要创建所有局部类对象。 |
代理 | ①隔离调用者和被调用者,降低耦合 ②可以增添新的操作以限制对被调用者的使用 |
①实现代理类需要额外的工作,从而增加了系统实现复杂度 |