设计模式
使用设计模式的最终目的是“高内聚低耦合”。
- 代码重用性:相同功能的代码,不用多次编写
- 代码可读性:编程规范性,便于他人阅读
- 代码扩展性:新增功能后,对原来代码没有影响
单一职责原则
设计的类尽量只负责一项功能,有以下优点
- 降低类的复杂性,一个类只负责一个职责
- 提高代码的可读性,逻辑清楚明了
- 降低风险,只修改一个类,并不影响其他类的功能。
- 在方法层面优化,减少I代码量
//使用前
public class single {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("汽车");
vehicle.run("轮船");
vehicle.run("飞机");
}
}
public class Vehicle {
void run(String type){
System.out.println(type+"在公路上开");
}
}
//Vehicle负责不止一个功能,设计存在问题
//使用后
public class single {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.runOnRoad("汽车");
vehicle.runOnWater("轮船");
vehicle.runOnAir("飞机");
}
}
public class Vehicle {
void runOnRoad(String type){
System.out.println(type+"在公路上开");
}
void runOnWater(String type){
System.out.println(type+"在水里开");
}
void runOnAir(String type){
System.out.println(type+"在天空开");
}
}
接口隔离原则
类不应该依赖他不需要的接口,接口尽量小粒度划分。如果将多个方法合并为一个接口,再提供给其他系统使用者的时候,就必须实现该接口的所有方法,有些方法是根本不需要的,造成使用者的混淆。
//使用前
public interface People {
void exam();
void teach();
}
public class Student implements People {
@Override
public void exam() {
System.out.println("学生考试");
}
@Override
public void teach() {
}
}
public class Teacher implements People{
@Override
public void exam() {
}
@Override
public void teach() {
System.out.println("教师教书");
}
}
public class test {
public static void main(String[] args){
People student=new Student();
student.exam();
People teacher=new Teacher();
teacher.teach();
}
}
//可以正常运行,但是想要实现People必须重写exam和teach
//使用后
public interface People1 {
void exam();
}
public class Student implements People1 {
@Override
public void exam() {
System.out.println("学生考试");
}
}
public class Teacher implements People2 {
@Override
public void teach() {
System.out.println("教师教书");
}
}
public class test {
public static void main(String[] args){
People1 student=new Student();
student.exam();
People2 teacher=new Teacher();
teacher.teach();
}
}
依赖倒转原则
高层模块不应该依赖底层模块,二者都应该依赖接口或者抽象类。
//使用前
public class Qingcai {
public void run(){
System.out.println("买到了青菜");
}
}
public class People {
public void bug(Qingcai qingcai){
qingcai.run();
}
}
public class test {
public static void main(String[] args){
People people=new People();
people.bug(new Qingcai());
}
}
//如果再添加一个萝卜需要修改大量代码,模块与模块之间耦合性太高
//使用后
public interface Shucai {
public void run();
}
public class Qingcai implements Shucai{
public void run(){
System.out.println("买到了青菜");
}
}
public class Luobo implements Shucai {
@Override
public void run() {
System.out.println("买到了萝卜");
}
}
public class People {
public void bug(Shucai shucai){
shucai.run();
}
}
public class test {
public static void main(String[] args){
People people=new People();
people.bug(new Qingcai());
people.bug(new Luobo());
}
}
里氏替换原则
继承的优点
- 提高代码的重用性,子类也有父类的属性和方法
- 提高代码的可扩展性,子类有自己独特的方法。
继承的缺点:当父类发生改变时,要考虑子类的修改
里氏替换原则是继承的继承,只有当子类替换父类时,软件功能仍不受影响,才说明父类真正被复用了。
- 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的抽象(已实现)方法。
- 子类可以有自己特有的方法
- 当子类覆盖或者实现父类方法时,方法的前置条件(即方法的形参)要比父类方法输入参数更宽松。
public class A {
public void run(HashMap hashMap){
System.out.println("父类执行");
}
}
public class B extends A{
//是重载而不是重写
public void run(Map map){
System.out.println("子类执行");
}
}
public class test {
public static void main(String[] args) {
A a = new A();
a.run(new HashMap());
System.out.println("将子类替换成父类:");
B b = new B();
//还是会执行父类方法,不符合里氏替换原则则会执行子类方法
b.run(new HashMap());
}
}
开闭原则
当应用需求改变时,在不修改软件实体的源代码或者二进制的前提下,可以扩展模块的功能,使其满足新需求。开闭原则是面向对象程序设计的最终目标,它使软件实体拥有一定的适应性和灵活性的同时具备稳定性和延续性。可以通过“抽象约束,封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。因为抽象灵活性好、适应性广,只要抽象合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重写派生一个实现类来扩展就可以了。
- 对软件测试的影响:只需对扩展的代码进行测试就可以了。
- 提高代码复用性:粒度越小,被复用的可能性就越大。
- 提高软件的可维护性
//使用前
public interface transport {
public void run();
}
public class Bus implements transport {
@Override
public void run() {
System.out.println("大巴在公路上跑");
}
}
//修改需求让大巴可以在水里开,在Bus类添加一个方法即可,但是违背了开闭原则,如果业务
//复杂,很容易出现问题
//使用后
public class universalBus extends Bus implements transport {
@Override
public void run() {
System.out.println("大巴既然在公路上开,又能在水里开");
}
}
迪米特原则
一个对象应该对其他对象保持最少了解,类与类关心越密切,耦合度越大。
//使用前
public class EmployeeManager {
public List<Employee> setValue(){
List<Employee> employees=new ArrayList<Employee>();
for(int i=0;i<10;i++){
Employee employee=new Employee();
employee.setId("总公司"+i);
employees.add(employee);
}
return employees;
}
public void printAllEmployee(SubEmployeeManager sub){
List<SubEmployee> list1 = sub.setValue();
for(SubEmployee e:list1){
System.out.println(e.getId());
}
List<Employee> list2 = this.setValue();
for(Employee e:list2){
System.out.println(e.getId());
}
}
}
public class SubEmployeeManager {
public List<SubEmployee> setValue(){
List<SubEmployee> subEmployees=new ArrayList<SubEmployee>();
for(int i=0;i<10;i++){
SubEmployee subEmployee=new SubEmployee();
subEmployee.setId("分公司"+i);
subEmployees.add(subEmployee);
}
return subEmployees;
}
}
public class test {
public static void main(String[] args){
EmployeeManager employeeManager=new EmployeeManager();
SubEmployeeManager subEmployeeManager=new SubEmployeeManager();
employeeManager.printAllEmployee(subEmployeeManager);
}
}
//EM类的打印方法是不符合迪米特原则的,可以只调用一个方法完成
//使用后
public class EmployeeManager {
public List<Employee> setValue() {
List<Employee> employees = new ArrayList<Employee>();
for (int i = 0; i < 10; i++) {
Employee employee = new Employee();
employee.setId("总公司" + i);
employees.add(employee);
}
return employees;
}
public void printAllEmployee(SubEmployeeManager sub) {
sub.printAllSubEmployee();
List<Employee> list2 = this.setValue();
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
public class SubEmployeeManager {
public List<SubEmployee> setValue(){
List<SubEmployee> subEmployees=new ArrayList<SubEmployee>();
for(int i=0;i<10;i++){
SubEmployee subEmployee=new SubEmployee();
subEmployee.setId("分公司"+i);
subEmployees.add(subEmployee);
}
return subEmployees;
}
public void printAllSubEmployee(){
List<SubEmployee> list1 = setValue();
for(SubEmployee e:list1){
System.out.println(e.getId());
}
}
}
合成复用原则
尽可能多的使用合成/集合,不要使用继承。
public class Instructor {
public String name;
public Instructor(String name) {
this.name = name;
}
public Instructor(Instructor instr) {
name = instr.name;
}
}
public class Textbook {
public String textname;
public Textbook(String name) {
this.textname = name;
}
}
public class Course {
public String courseName;
public Instructor instrcutor;
public Textbook textbook;
public Course(String name,Instructor instr,Textbook text) {
courseName = name;
instrcutor = new Instructor(instr);
//新开辟了空间,传入的是内容,称为组合
textbook = text;
//没有新开辟空间,传入的是地址,称为聚合
}
}
public class aggregationAndComposition {
public static void main(String[] args) {
Instructor instr1 = new Instructor("teng");
Textbook text1 = new Textbook("kkk");
Course course1 = new Course("Java",instr1,text1);
System.out.println(course1.courseName + " " + course1.instrcutor.name + " " + course1.textbook.textname);
System.out.println();
instr1.name = "yao";
text1.textname = "jjj";
System.out.println(course1.courseName + " " + course1.instrcutor.name + " " + course1.textbook.textname);
}
}
//改变Instructor,Textbook前的输出 Java teng kkk
//改变Instructor,Textbook后的输出 Java teng jjj
在上面的这个简单的小例子中,我们可以清楚的发现,在改变Instrcutor和Textbook的值后,course中的instructor未发生变化,而textbook的值发生了变化,这是因为我们在Course的构造函数中定义instructor时开辟了一个新的空间,传入的是内容(相当于把instr重新复制了一遍赋值给Course的instructor),因此在改变外面instr的值时,course内的instr值不受影响,不变化,这就是深拷贝,也就叫做组合;在定义Course的textbook时没有新开辟空间,只是把text的值直接赋给了Course中的textbook,text就是一个指针,所以赋值过去的就是一个地址,因此改变外面text的值时,course内的textbook值也会受到影响,这就是浅拷贝,也就叫做聚合。