在面向对象的设计过程中, 我们要对代码进行一个设计, 从而提高一个软件系统的可维护性和可复用性, 那么遵从面向对象的设计原则,可以在进行设计方案时减少错误设计的产生,从不同的角度提升一个软件结构的设计水平。
面向对象有以下七大原则:
1.单一职责原则:
单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小, 对于单一职责原则,可以理解为一个类只负责一个功能领域中的相应职责,即一个类不要负责太多“杂乱”的工作。
在软件系统中,如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时设计或遭受到意想不到的破坏。
以项目开发为例,如果项目组成员每个人的职责都很明确,可以专心开发自己负责的模块,则项目成果的质量往往很高。相反,如果职责不清晰,分工就会混乱。
优点: 低耦合,高内聚
2.开闭原则
开闭原则即对扩展开放,对修改封闭。在软件系统开发过程中,软件的需求往往会随着时间的推移而发生变化。因此,进行软件设计时需要考虑怎样的设计才能面对需求的改变却可以相对保持稳定,从而使得系统可以在第一个版本以后不断推出新的版本,这时便可以以开闭原则作为指导。
为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键, 在进行软件设计时,一般先评估出最有可能发生变化的类,然后构造抽象来隔离那些变化。当变化发生时,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。
![](https://i-blog.csdnimg.cn/blog_migrate/37a61b16792861c80f05fc039805093d.png)
示例:
(1)不使用开闭原则的功能的实现
public class CarDemo {
public static void main(String[] args) {
new CarFactory().createCar(1);
new CarFactory().createCar(2);
new CarFactory().createCar(3);
new CarFactory().createCar(4);
new CarFactory().createCar(5);
}
}
/*
汽车工程类,专门负责造汽车
*/
class CarFactory{
/*
违反了开闭原则,后期如果添加新的汽车类,则需要修改代码
*/
public void createCar(int type){
if(type==1){
System.out.println("造宝马汽车"+new Car("宝马汽车"));
}else if(type==2){
System.out.println("造奥迪汽车"+new Car("奥迪汽车"));
}else{
System.out.println("造大众汽车"+new Car("大众汽车"));
}
}
}
class Car{
String name;
public Car(String name) {
this.name = name;
}
}
上述功能的实现需要修改原代码, 每一次新添加一个汽车类, 都需要新增加代码,实现起来非常麻烦
(2)使用开闭原则
class CarDemo{
public static void main(String[] args) {
new CarFactory().carfactory(new BMW());
new CarFactory().carfactory(new Aodi());
new CarFactory().carfactory(new DaZhong());
}
}
/*
汽车工程类,专门负责造汽车
*/
class CarFactory{
void carfactory(Car car){
car.createCar();
}
}
//抽象汽车类
abstract class Car{
public abstract void createCar();
}
//宝马
class BMW extends Car{
@Override
public void createCar() {
System.out.println("造宝马汽车");
}
}
//奥迪
class Aodi extends Car{
@Override
public void createCar() {
System.out.println("造奥迪汽车");
}
}
//大众
class DaZhong extends Car{
@Override
public void createCar() {
System.out.println("造大众汽车");
}
}
//奔驰
class BC extends Car{
@Override
public void createCar() {
System.out.println("造奔驰汽车");
Calendar.getInstance();
new GregorianCalendar();
}
}
从以上代码可以看出来, 我每新添加一个功能只需要新添加一个类, 而不用修改原来的代码
这样会使得代码适用性和灵活性提高, 稳定性和延续性增强, 也拥有了较高的可复用性和可维护性
3.里氏替换原则
此原则是针对继承提出的, 虽然继承有很大的优势, 可以提高代码的复用性和可扩展性, 但是继承是侵入式的, 只要继承就必须拥有父类的属性和方法,体系结构复杂, 而且继承机制很大的增加了耦合性(父类被子类继承,父类功能修改会影响子类)
也就是说子类继承父类后,尽量不要重写父类的方法,可以新增扩展其他的功能, 保证子类功能的正确性. 不能让功能修改后,导致程序出错.
示例:
public class CalculatorDemo{
public static void main(String[] args) {
System.out.println(new SuperCalculator().sum(5,5,5));
}
}
//计算器 基类
class Calculator {
//加法
public int add(int a,int b){
return a+b;
}
//减法
public int sub(int a,int b){
return a-b;
}
}
/*
超级计算器子类
*/
class SuperCalculator extends Calculator{
//重写了父类加法
@Override
public int add(int a, int b) {
return a+b+5;
}
//求和方法 子类新增的功能
public int sum(int a,int b,int c){
//调用add(),但是子类重写了父类方法,此处调用的子类方法发生了变化
int result = this.add(a,b);
return result+c;
}
}
以上代码可以可看出,子类重写了父类的add方法后, 子类所调用的默认的add方法就是自己重写后的方法, 而子类重写后更改了add方法的功能, 本来不重写默认调用的是父类中的add方法算下来是15, 但是子类重写后又加了5, 所以结果是20.
里氏替换原则克服了子类继承父类重写方法后可复用性变差的问题, 也提高了代码的可维护性, 降低了需求变更时引入的风险.
4.依赖倒置原则
上层模块不应该依赖底层模块,它们都应该依赖于抽象, 抽象不应该依赖于细节,细节应该依赖于抽象, 也就是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。也就是针对抽象层编程,面向接口编程
![](https://i-blog.csdnimg.cn/blog_migrate/f9eb7943a88f7fae7e6b9f23e3138b34.png)
示例:
/*
依赖倒置引入案例
*/
public class WorkerDemo{
public static void main(String[] args) {
new Worker().getMessage(new DingDing());
new Worker().getMessage(new WeChat());
}
}
class Worker {
public void getMessage(DingDing ding){
System.out.println(ding.sendMessage());
}
public void getMessage(WeChat weChat){
System.out.println(weChat.sendMessage());
}
}
//钉钉消息
class DingDing{
public String sendMessage(){
return "钉钉消息";
}
}
//微信消息
class WeChat{
public String sendMessage(){
return "微信消息";
}
}
以上程序实现了不同平台发消息的功能, 如果要新增加一个平台或者更改一个平台就要对具体的实现(class Worker)进行更改
/*
依赖倒置案例演示
*/
public class WorkerDemo{
public static void main(String[] args) {
new Worker().getMessage(new WeChat());
}
}
class Worker {
public void getMessage(Message message){
System.out.println(message.sendMessage());
}
}
interface Message{
public String sendMessage();
}
class WeChat implements Message{
@Override
public String sendMessage() {
return "微信消息";
}
}
class DingDing implements Message{
@Override
public String sendMessage() {
return "钉钉消息";
}
}
如果我们定义一个接口, 每一个平台实现这个接口, 如果要更改平台, 不需要再具体的实现上进行修改, 从而降低客户与实现模块之间的耦合.
5.迪米特原则
它要求一个对象应该对其他对象有最少的了解,所以迪米特法则又叫做最少知识原则.
只和你的直接朋友交谈,不跟“陌生人”说话
直接朋友:
1. 类中的成员属性.
2. 在类中的方法作为参数使用.
3. 在类中的方法作为返回值类型.
迪米特法则的核心是降低类之间的耦合, 从被依赖者的角度来说,尽量将逻辑封装在类的内部,对外除了提供的public 方法,不泄露任何信息, 从依赖者的角度来说,只依赖应该依赖的对象, 切忌不要为了用而用
示例:
public class Demeter {
public static void main(String[] args) {
new SchoolManger().printAllEmployee(new CollegeManger());
}
}
/*
学校员工类
*/
class SchoolEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
/*
学院员工类
*/
class CollegeEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
//学院员工管理管理类
class CollegeManger{
//生成学院所有的员工
public List<CollegeEmployee> getCollegeEmployee(){
ArrayList<CollegeEmployee> collegeEmployeeArrayList = new ArrayList<>();
for (int i = 0; i <10 ; i++) {
CollegeEmployee collegeEmployee = new CollegeEmployee();
collegeEmployee.setId("学院员工的id="+i); //添加学院员工
collegeEmployeeArrayList.add(collegeEmployee);
}
return collegeEmployeeArrayList;
}
}
//学校员工管理类
class SchoolManger {
//生成学校的员工
public List<SchoolEmployee> getSchoolEmployee() {
ArrayList<SchoolEmployee> employeeArrayList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SchoolEmployee employee = new SchoolEmployee();
employee.setId("学校的员工id=" + i);
employeeArrayList.add(employee);
}
return employeeArrayList;
}
//输出学校员工和学院员工信息
public void printAllEmployee(CollegeManger collegeManger) {
//获取到学校员工
List<SchoolEmployee> employeeArrayList = this.getSchoolEmployee();
System.out.println("--------学校员工--------");
for (SchoolEmployee employee1 : employeeArrayList) {
System.out.println(employee1.getId());
}
System.out.println("--------学院员工--------");
List<CollegeEmployee> collegeEmployees = collegeManger.getCollegeEmployee();
//此处学校管理类中出现CollegeEmployee,此类与SchoolManger并非直接朋友,不合理
for (CollegeEmployee collegeEmployee : collegeEmployees) {
System.out.println(collegeEmployee.getId());
}
}
}
最后在学校管理类中出现了不是它的朋友的类, 所以不太合理
6.接口隔离原则
使用多个接口,而不使用单一的总接口,不强迫新功能实现不需要的方法。
![](https://i-blog.csdnimg.cn/blog_migrate/8456476cc510ac01e70d1b702a7e9d40.png)
7.组合/聚合复用原则
优先使用组合,使系统更灵话,其次才考虑继承,达到复用的目的。一般而言,如果两个类之是"Has-A"关系应使用组合或聚合,如果是"Is-A"关系可使用继承。
案例:现在假设有一个 A 类,里面有两个方法,有一个类 B,想要复用这两个方法,请问有几种方案?
示例:
(1)继承实现
/*
组合/聚合复用原则案例1 使用依赖实现复用
*/
public class A {
public void method01() {
}
public void method02() {
}
}
class B extends A {
private A a;
public void method() {
}
}
class Test {
public static void main(String[] args) {
new B().method01();
new B().method02();
}
}
(2)使用关联的方法
/*
组合/聚合复用原则案例2 使用组合/聚合实现复用
*/
public class A {
public void method01(){
}
public void method02(){
}
}
class B{
A a;
public void setA(A a){
this.a = a;
}
public void use(){
a.method01();
a.method02();
}
}
class Test{
public static void main(String[] args) {
A a = new A();
B b = new B();
b.setA(a);
b.use();
}
}
(3)依赖复用的方法,耦合性降低
/*
组合/聚合复用原则案例3 使用依赖实现复用
*/
public class A {
public void method01(){
}
public void method02(){
}
}
class B{
public void use(A a){
a.method01();
a.method02();
}
}
class Test{
public static void main(String[] args) {
A a = new A();
B b = new B();
b.use(a);
}
}
总结:
开闭原则:要求对扩展开放,对修改关闭
里氏替换原则:不要破坏继承体系
依赖倒置原则:要求面向接口编程
单一职责原则:实现类职责要单一
接口隔离原则:在设计接口的时候要精简单一
迪米特法则:只与直接的朋友的通信
合成复用原则:尽量使用聚合和组合的方式,而不是使用继承
设计原则的核心思想
找出应用中可能需要变化之处,独立出来,不要和不需要变化的代码混在一起
针对接口编程,而不是针对实现编程
为了交互对象的松耦合设计而努力
遵循设计原则:就是为了让程序高内聚,低耦合