程序员编程应该遵守的原则,也是设计模式的设计依据(设计模式为什么这样设计的依据,因为遵守了这七大原则)
单一职责原则
- 概述:以一个类来说,一个类只负责一项职责,要是一个类负责两个不同的职责:职责1,职责2,当职责1因为后期需求变更需要改变这个类时,可能会造成职责2执行错误,所以可以将这个类分为类1和类2,分别承担这两项职责。
package com.itguigu.principle.singleresoponsibility;
public class SingleBility1 {
public static void main(String[] args) {
Vehicle vehicle=new Vehicle();
vehicle.run("摩托车");
vehicle.run("汽车");
vehicle.run("飞机");
}
}
class Vehicle{
public void run(String vehicle) {
System.out.println(vehicle+"在公路上行驶");
}
}
/*输出:
摩托车在公路上行驶
汽车在公路上行驶
飞机在公路上行驶
*/
//run()方法中违反了单一职责原则
//解决方案:根据交通工具运行方式不同,分解成不同的类-》方案二```
package com.itguigu.principle.singleresoponsibility;
public class SingleBility2 {
public static void main(String[] args) {
RoadVehicle road=new RoadVehicle();
AirVehicle air=new AirVehicle();
WaterVehicle water=new WaterVehicle();
road.run("摩托车");
road.run("汽车");
air.run("飞机");
water.run("轮船");
}
}
class RoadVehicle{
public void run(String vehicle) {
System.out.println(vehicle+"在公路上行驶");
}
}
class AirVehicle{
public void run(String vehicle) {
System.out.println(vehicle+"在天上上行驶");
}
}
class WaterVehicle{
public void run(String vehicle) {
System.out.println(vehicle+"在水上上行驶");
}
}
/*输出:
摩托车在公路上行驶
汽车在公路上行驶
飞机在天上上行驶
轮船在水上上行驶
*/
//遵守了单一职责原则,
//改动很大,花销很大,将类分解还要修改客户端
//改进:直接修改,改动代码少-》方案三
package com.itguigu.principle.singleresoponsibility;
public class SingleBility3 {
public static void main(String[] args) {
Vehicle3 vehicle=new Vehicle3();
vehicle.run("摩托车");
vehicle.runWater("轮船");
vehicle.runAir("飞机");
}
}
class Vehicle3{
public void run(String vehicle) {
System.out.println(vehicle+"在公路上行驶");
}
public void runAir(String vehicle) {
System.out.println(vehicle+"在天上行驶");
}
public void runWater(String vehicle) {
System.out.println(vehicle+"在水里上行驶");
}
}
/*输出:
摩托车在公路上行驶
轮船在水里上行驶
飞机在天上行驶
*/
//这种修改方法没有对原来的类做大的修改,只是增加了方法,
//没有完全遵守单一职责,只在方法上遵守单一职责```
注意事项:
降低类的复杂度,一个类只负责一项职责
提高类的可读性,可维护性
降低变更引起的风险
在逻辑足够简单,才能在类的级别违反单一职责原则,而只在方法级别遵守单一职责原则
接口隔离原则
- 概述:客户端不应该依赖他不需要的接口,即一个类对另外一个类的依赖应该建立在最小的接口上【就是一个类依赖一个接口应该是最少的实现方法,不要有多余的方法存在,比如有6个方法,但是只依赖三个,这种就不是最小接口,即违反了接口隔离原则】
举个栗子:引入以下问题
- 依赖关系:
- 依据类图编写代码实现:
package com.itguigu.principle.InterfaceSegregatiob;
public class Segregation1 {
public static void main(String[] args) {
}
}
class A{//A类通过interface1依赖(使用)B类,但是只会用到123方法,别的都浪费了
public void depend1(interface1 inter) {
inter.operation1();
}
public void depend2(interface1 inter) {
inter.operation2();
}
public void depend3(interface1 inter) {
inter.operation3();
}
}
class C{//C类通过interface1依赖(使用)D类,但是只会用到145方法,别的都浪费了
public void depend1(interface1 inter) {
inter.operation1();
}
public void depend4(interface1 inter) {
inter.operation4();
}
public void depend5(interface1 inter) {
inter.operation5();
}
}
interface interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements interface1{
public void operation1() {
System.out.println("B-----operation1");
}
public void operation2() {
System.out.println("B-----operation2");
}
public void operation3() {
System.out.println("B-----operation3");
}
public void operation4() {
System.out.println("B-----operation4");
}
public void operation5() {
System.out.println("B-----operation5");
}
}
class D implements interface1{
public void operation1() {
System.out.println("D-----operation1");
}
public void operation2() {
System.out.println("D----operation2");
}
public void operation3() {
System.out.println("D-----operation3");
}
public void operation4() {
System.out.println("D-----operation4");
}
public void operation5() {
System.out.println("D-----operation5");
}
}
上面方法不满足隔离原则【因为类B和类D都必须去实现类A和类B一些不需要的方法
解决办法:是将interface1拆分称为几个独立的接口,类A和类C分别与他们需要的接口建立依赖关系。这就是接口隔离原则【要保证最小接口(把大的接口拆成小的接口)】
- 改进后满足接口隔离原则的依赖关系:
- 改进后的代码实现:
package com.itguigu.principle.InterfaceSegregatiob;
public class Segregation2 {
public static void main(String[] args) {
A a = new A();
a.depend1(new B());//A类通过接口去依赖B类
a.depend2(new B());
a.depend3(new B());
}
}
class A{ //A类通过interface1,interface2去依赖B类,去使用B类的一些方法
public void depend1(interface1 inter) {
inter.operation1();
}
public void depend2(interface2 inter) {
inter.operation2();
}
public void depend3(interface2 inter) {
inter.operation3();
}
}
class C{ //C类通过interface1,interface2去依赖D类,去使用类D的一些方法
public void depend1(interface1 inter) {
inter.operation1();
}
public void depend4(interface3 inter) {
inter.operation4();
}
public void depend5(interface3 inter) {
inter.operation5();
}
}
interface interface1{
void operation1();
}
interface interface2{
void operation2();
void operation3();
}
interface interface3{
void operation4();
void operation5();
}
class B implements interface1,interface2{
public void operation1() {
System.out.println("B-----operation1");
}
public void operation2() {
System.out.println("B-----operation2");
}
public void operation3() {
System.out.println("B-----operation3");
}
}
class D implements interface1,interface3{
public void operation1() {
System.out.println("D-----operation1");
}
public void operation4() {
System.out.println("D-----operation4");
}
public void operation5() {
System.out.println("D-----operation5");
}
}
依赖倒置(倒转)原则
- 概述:
①高层模块不应该依赖低层模块,二者都应该依赖其抽象(抽象类或者接口),不要去依赖一个具体的子类
②抽象不应该依赖细节,细节应该依赖抽象
③依赖倒转(倒置)的中心思想是面向接口编程
④依赖倒置原则是的设计理念是:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类
⑤使用接口或者抽象类的目的是制定好规范,而不是设计任何具体的操作,把展现细节的任务交给他们的实现类去完成。
举个栗子:
- 引入问题:完成一个Person类接收消息的功能
- 传统的方式
package com.itguigu.principle.inversion;
//依赖倒转原则
public class Invertion{
public static void main(String[] args) {
new Person().receive(new Email());
}
}
//完成一个Person接收消息的功能
//传统方式完成:
/*分析:
* 1.简单,容易实现,容易想到
* 2.如果要接收微信,短信等,又要新增类,而且Person类也要增加相应的方法
* 3.解决思路:引入一个抽象的接口IReceiver表示接收者,这样Person类与此接口发生依赖
* 因为:Email与微信,短信等都属于要接收的范围,他们可以各自实现Receiver的接口,这样就满足了依赖倒转原则
* 体现了接口编程,细节依赖抽象
* */
class Person{
public void receive(Email email) {
System.out.println(email.getInfo());
}
}
class Email{
public String getInfo() {
return "电子邮件:"+"love you";
}
}
- 改进后符合依赖倒转原则的方式
package com.itguigu.principle.inversion;
public class Invertion {
public static void main(String[] args) {
new Person().receive(new Email());
new Person().receive(new Wechat());
}
}
class Person{
public void receive(IReceiver re){
System.out.println(re.getInfo());
}
}
//完成一个Person接收消息的功能
interface IReceiver{
String getInfo();
}
class Email implements IReceiver{
public String getInfo() {
return "我是电子邮件:hello";
}
}
class Wechat implements IReceiver{//对接口的依赖,稳定性比较好
public String getInfo() {
return "我是微信:hi";
}
}
- 依赖关系传递的三种方式和应用实例
①接口传递
②构造方法传递
③setter方式传递
- 注意事项:
《1》低层模块尽量都要有抽象类或接口,或者两者都有,这样程序的稳定性会更
《2》变量的声明类型尽量是抽象类或者接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化(对象与其引用之间有个缓冲层,就是对象的引用类型是其父类,而不是其本身,要扩展功能时,只需要在父类加,那么子类自然就有了)
《3》继承时遵循里式替换原则
里式替换原则
-
概述:
父类中凡是已经实现好的方法,实际上是设定的规范和契约
继承带来弊端,程序之间增大了耦合关系,父类修改,子类会被影响
正确使用继承就要满足,里式替换原则 -
概念:引用基类的地方必须能够透明的使用其子类的对象
-
要求:在使用继承时,遵循里式替换原则,在子类中尽量不要重写父类的方法
-
继承让两个类的耦合性增强了,在适当的情况下,可以通过聚合,组合或者依赖来解决问题
-
引入问题
package com.itguigu.principle.Loskev;
public class Liskov {
public static void main(String[] args) {
//问题:方法被无意之间重写了,这造成了错误
//解决:将原来的父类和子类都集成一个更通俗的基类,
//原有的继承关系去掉,采用依赖,聚合,组合灯关系代替
A a = new A();
System.out.println("11-3="+a.func1(11,3));
System.out.println("1-8="+a.func1(1,8));
System.out.println("---------------------");
B b = new B();
System.out.println("11-3="+b.func1(11,3));//本意是调用父类的方法
System.out.println("1-8="+b.func1(1,8));
System.out.println("11+3+9="+b.func1(11,3));
}
}
class A{//返回两个数的差
public int func1(int num1,int num2) {
return num1-num2;
}
}
class B extends A{//新增两个数相加再加9
//无意间重写了父类方法
public int func1(int num1,int num2) {
return num1+num2;
}
public int func2(int num1,int num2) {
return func1(num1,num2)+9;
}
}
- 解决方法:
package com.itguigu.principle.Loskev;
public class Liskov {
public static void main(String[] args) {
A a = new A();
System.out.println("11-3="+a.func1(11,3));
System.out.println("1-8="+a.func1(1,8));
System.out.println("---------------------");
B b = new B();//因为B不再继承A类,因此调用者不会再使用func1求减法
//调用功能很明确
System.out.println("11+3="+b.func1(11,3));//本意是调用父类的方法
System.out.println("1+8="+b.func1(1,8));
System.out.println("11+3+9="+b.func1(11,3));
//使用组合仍然可以调用到A类的方法
System.out.println("11-3="+new B().fun3(11,3));
}
}
//创建一个更加基础的类
class Base{//把更加基础的方法和成员写到Base类
}
class A extends Base{
//返回两个数的差
public int func1(int num1,int num2) {
return num1-num2;
}
}
//B类继承了A
//增加了一个新功能:完成两个数相加,然后和9求和
class B extends Base{
//如果B需要使用A类的方法,使用组合关系
private A a = new A();
//这里,重写了A类的方法,可能是无意识
public int func1(int num1,int num2) {
return num1+num2;
}
public int func2(int num1,int num2) {
return func1(num1,num2)+9;
}
//我们仍然使用A的方法
public int fun3(int num1,int num2){
return this.a.func1(num1, num2);
}
}
开闭原则
-
概述:
《1》开闭原则是编程中最基础,最重要的设计原则
《2》一个软件实体,如类,模块和函数应该对扩展开放【对提供方】,对修改关闭【使用方】。用抽象构建框架,用实现扩展细节。
《3》当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
《4》编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则
举个栗子: -
题目:绘制图形
-
这种方法存在的优缺点:
优点:简单
缺点:违反了OCP原则,使用方和提供方都修改了代码,当我们要增加新的功能时,尽量不修改或者少修改代码。当我们再次添加一个新的图形种类时,需要再做修改 -
代码演示:
package com.itguigu.principle.ocp;
public class Ocp {
public static void main(String[] args) {
// TODO Auto-generated method stub
GraphicEditor ga = new GraphicEditor();
ga.drawShape(new Rectangle());
ga.drawShape(new Circle());
}
}
class GraphicEditor{
//接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s){
if(s.m_type==1){
drawRectangle(s);
}else if(s.m_type==2){
drawCircle(s);
}
}
//绘制矩形
public void drawRectangle(Shape r){
System.out.println("绘制矩形");
}
//绘制圆形
public void drawCircle(Shape r){
System.out.println("绘制圆形");
}
}
class Shape{
int m_type;
}
class Rectangle extends Shape{
Rectangle(){
super.m_type=1;
}
}
class Circle extends Shape{
Circle(){
super.m_type=2;
}
}
- 改进后满足OCP(开闭原则)的代码
package com.itguigu.principle.ocp;
public class Ocp {
public static void main(String[] args) {
// TODO Auto-generated method stub
GraphicEditor ga = new GraphicEditor();
ga.drawShape(new Rectangle());
ga.drawShape(new Circle());
}
}
class GraphicEditor{
//接收Shape对象,然后根据type,来绘制不同的图形
public void drawShape(Shape s){
s.draw();
}
}
abstract class Shape{
int m_type;
public abstract void draw();
}
class Rectangle extends Shape{
Rectangle(){
super.m_type=1;
}
public void draw(){
System.out.println("绘制矩形");
}
}
class Circle extends Shape{
Circle(){
super.m_type=2;
}
public void draw(){
System.out.println("绘制圆形");
}
}
迪米特法则(最少知道原则)
- 概述
《1》一个对象应该对其他对象保持最少的了解
《2》类与类关系越密切,耦合度越大
《3》迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。页就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息。
《4》迪米特法则还有个更简单的定义:只与直接的朋友通信
《5》直接的朋友:每个对象都会与其他对象由耦合关系,只要两个对象之间有耦合关系,我们就说两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的 形式出现在类的内部。
举个栗子: - 题目:有一个学校,下属有各个学院和总部,现要求打印出学校总部员工ID和学院员工的id。
- 代码实现:
package com.itguigu.principle.Demeter;
import java.util.ArrayList;
import java.util.List;
//迪米特法则(最少知道法则)
//客户端使用
public class Demeter1 {
public static void main(String[] args) {
//创建了一个SchoolManager对象
SchoolManager school = new SchoolManager();
//输出学院员工id,和学校总部的员工id
school.printAllEmployee(new CollegeManager());
}
}
//学校总部员工
class Employee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//学院的员工
class CollegeEmployee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//管理学院员工的管理类
class CollegeManager{
//返回学院的所有员工
public List<CollegeEmployee> getAllEmployee(){
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
//增加了十个员工
for(int i = 0;i<10;i++){
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id="+i);
list.add(emp);
}
return list;
}
}
//学校管理类
/*分析:
* Employee(方法返回值),CollegeManager(方法参数)是直接朋友,
* CollegeEmployee不是直接朋友
* 违背了迪米特法则:陌生的类不要以局部变量的方式出现
*
* */
class SchoolManager{
//返回学校总部的所有员工
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList<Employee>();
//增加了五个员工
for(int i = 0;i<5;i++){
Employee emp = new Employee();
emp.setId("学校总部员工id="+i);
list.add(emp);
}
return list;
}
//完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub){
/*1.这里的CollegeEmployee不是SchoolManager的直接朋友
* 2.CollegeEmployee是以局部变量的方式出现在SchoolManager
* 3.违反了迪米特法则*/
List<CollegeEmployee> list1 = sub.getAllEmployee();
System.out.println("-----------分公司员工-------------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
List<Employee> list2 = this.getAllEmployee();
System.out.println("-----------学校总部员工-------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
- 修改后符合迪米特法则的代码
package com.itguigu.principle.Demeter;
import java.util.ArrayList;
import java.util.List;
//迪米特法则(最少知道法则)
//客户端使用
public class Demeter {
public static void main(String[] args) {
//创建了一个SchoolManager对象
SchoolManager school = new SchoolManager();
//输出学院员工id,和学校总部的员工id
school.printAllEmployee(new CollegeManager());
}
}
//学校总部员工
class Employee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//学院的员工
class CollegeEmployee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//管理学院员工的管理类
class CollegeManager{
//返回学院的所有员工
public List<CollegeEmployee> getAllEmployee(){
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
//增加了十个员工
for(int i = 0;i<10;i++){
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id="+i);
list.add(emp);
}
return list;
}
//添加了这个方法
public void printEmployee(){
List<CollegeEmployee> list1 = getAllEmployee();
System.out.println("-----------分公司员工-------------");
for (CollegeEmployee e : list1) {
System.out.println(e.getId());
}
}
}
//学校管理类
class SchoolManager{
//返回学校总部的所有员工
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList<Employee>();
//增加了五个员工
for(int i = 0;i<5;i++){
Employee emp = new Employee();
emp.setId("学校总部员工id="+i);
list.add(emp);
}
return list;
}
//完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub){
//分析问题:
//1.将输出学院的员工方法,封装到Collegemanager,不要在别的类里面处理自己的逻辑
sub.printEmployee();
List<Employee> list2 = getAllEmployee();
System.out.println("-----------学校总部员工-------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
- 总结:把自己类里面的处理逻辑封装好,只对外提供方法,不要把在别的类里面处理自己的逻辑;迪米特法则是降低类之间的耦合;但是值得注意的是,由于每个类都减少不必要的依赖,因此迪米特方法只是要求降低类间(对象间)耦合关系,并不是完全没有依赖关系
合成复用法则
- 概述:原则是尽量使用合成或聚合的方式,而不是使用继承
总结
- 找出应用中可能需要变化之外,把他们独立出来,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 为了交互对象之间的松耦合设计而努力
- 工厂,单例,装饰,适配器,代理