前言:
今天兄弟们对“java设计原则详解”大约比较关怀,小伙伴们都需要分析一些“java设计原则详解”的相关知识。那么小编在网摘上收集了一些对于“java设计原则详解””的相关文章,希望小伙伴们能喜欢,咱们一起来学习一下吧!设计模式不是一种新的语言,也不是什么新api,更不是什么新语法。设计模式是前辈们不断总结,不断打磨出的设计方法。不同的设计模式适用于不同的场景。设计模式公认的有23种,分别对应23种不同的设计场景。千万不要认为任何一种设计模式能解决任何问题,每一种设计模式只能用于适用的场景,不是万能的。设计模式不是万能的,有优点也有缺点,不能为了适用设计模式而使用设计模式。切记防止‘’‘模式的滥用’23种设计模式,背后其实是7大设计原则。也就是说,每一个设计模式都归属于一个或者多个设计原则。七大设计原则背后总结为一个字:分!
1.单一职责原则
百科定义:单一职责原则(SRP:Single responsibility principle)又称单一功能原则,它规定一个类应该只有一个发生变化的原因。通俗的讲就是每个方法,每个类,每个框架都只负责一件事情。
例如:Math.round()只负责完成四舍五入方法,其它不管(方法)
Read类只负责文件的读取(类)
SpringMVC只负责简化MVC的开发(框架)
反例:
/** * <p> * <p> * * @author xmp * @since 2020/9/11 */ public class NegtiveTest { public static void main(String[] args) throws IOException { //统计一个文本文件中有多少个单词 Reader reader = new FileReader("C:\\Users\\asus\\Desktop\\2.txt"); BufferedReader bufferedReader = new BufferedReader(reader); String line = null; StringBuilder stringBuilder = new StringBuilder(); while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line); stringBuilder.append(" "); } String[] strings = stringBuilder.toString().split("[^a-zA-Z]+"); System.out.println(strings.length); bufferedReader.close(); } } //以上代码违反了单一职责原则: //1.代码重用性低 //2.可读性低
正例:
/** * <p> * <p> * * @author xmp * @since 2020/9/11 */ public class PositiveTest { public static void main(String[] args) throws IOException { //统计一个文本文件中有多少个单词 String string = loadFile("C:\\Users\\asus\\Desktop\\2.txt"); String[] strings = getWords(string); System.out.println(strings.length); } /** * 只负责返回单词数组 * @param string * @return */ private static String[] getWords(String string) { String[] strings = string.toString().split("[^a-zA-Z]+"); return strings; } /** * 只负责读取文件 * * @param path * @return * @throws IOException */ private static String loadFile(String path) throws IOException { Reader reader = new FileReader(path); BufferedReader bufferedReader = new BufferedReader(reader); String line = null; StringBuilder stringBuilder = new StringBuilder(); while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line); stringBuilder.append(" "); } bufferedReader.close(); return stringBuilder.toString(); } }2.开闭原则(OCP)
百科定义:在面向对象编程领域中,开闭原则规定“软件中的对象(类**,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”。正确的理解是,对扩展新功能开放,对修改旧功能关闭
反例:
/** * <p> * <p> * * @author xmp * @since 2020/9/16 */ @Data public class Cellphone { private String brand; private Double price; public void setPrice(Double price) { this.price = price * 0.8; } public static void main(String[] args) { Cellphone cellphone = new Cellphone(); cellphone.setBrand("华为"); cellphone.setPrice(1999.0); //所有手机打8折,违反开闭原则的做法是打开Cellphone源码,在setPrice上修改 System.out.println(cellphone); } }
正例:
/** * <p> * <p> * * @author xmp * @since 2020/9/16 */ @Data public class Cellphone { private String brand; private Double price; public void setPrice(Double price) { this.price = price; } public static void main(String[] args) { Cellphone cellphone = new DiscountCellphone(); cellphone.setBrand("华为"); cellphone.setPrice(1999.0); //所有手机打8折,符合开闭原则的做法是新建一个子类,继承Cellphone重写父类setPrice方法 System.out.println(cellphone); } } class DiscountCellphone extends Cellphone { @Override public void setPrice(Double price) { super.setPrice(price * 0.8); } }3.接口隔离原则
1.客户端不应该依赖它不需要的接口
2.类间的依赖关系应该建立在最小接口上
反例:
/** * <p> * <p> * * @author xmp * @since 2020/9/16 */ public class InterfaceSegregationNegativeTest { public static void main(String[] args) { Animal animal = new Dog(); // animal.fly(); animal.run(); } } interface Animal { void run(); void swim(); void fly(); } class Dog implements Animal{ @Override public void run() { System.out.println("Dog is running"); } @Override public void swim() { System.out.println("Dog is swimming"); } //狗不具备飞行的能力,不应该实现飞行的方法 @Override public void fly() { throw new RuntimeException("Dog can not fly"); } }
正例:
/** * <p> * <p> * * @author xmp * @since 2020/9/16 */ public class InterfaceSegregationPositiveTest { public static void main(String[] args) { Dog dog = new Dog(); dog.run(); Bird bird = new Bird(); bird.fly(); } } interface RunAble { void run(); } interface SwimAble { void swim(); } interface Flyable { void fly(); } /** * 狗会跑,会游泳,客户端只需要实现它需要的接口 */ class Dog implements RunAble, SwimAble { @Override public void run() { System.out.println("Dog is running"); } @Override public void swim() { System.out.println("Dog is swimming"); } } /** * 鸟会飞,只需要实现它需要的接口 */ class Bird implements Flyable { @Override public void fly() { System.out.println("Bird is flying"); } }4.依赖倒置原则
上层不应该依赖于下层,上层和下层都应该依赖于抽象的接口
反例:
/** * <p> * <p> * * @author xmp * @since 2020/9/16 */ public class DependencyInversionNegativeTest { public static void main(String[] args) { //Person类依赖于Dog和Cat类,如果要喂鸟等其它动物,就要改Person的源代码,不利于扩展 Person person = new Person(); Cat cat = new Cat(); cat.setName("小猫"); Dog dog = new Dog(); dog.setName("小狗"); person.feed2(dog); } } class Person { void feed(Cat cat) { System.out.println("人正在喂" + cat.getName()); cat.eat(); } void feed2(Dog dog) { System.out.println("人正在喂" + dog.getName()); dog.eat(); } } @Data class Cat { String name; void eat() { System.out.println("猫正在吃猫粮"); } } @Data class Dog { String name; void eat() { System.out.println("狗正在吃狗粮"); } }
正例:
/** * <p> * <p> * * @author xmp * @since 2020/9/16 */ public class DependencyInversionPositiveTest { public static void main(String[] args) { //Person类、Dog类、Cat类都依赖于抽象的Animal接口,如果需要喂鸟只需要新建一个Bird类实现Animal即可,利于扩展 Person person = new Person(); Cat cat = new Cat(); cat.setName("小猫"); Dog dog = new Dog(); dog.setName("小狗"); person.feed(cat); person.feed(dog); } } class Person { void feed(Animal animal) { animal.eat(); } } interface Animal { void eat(); } @Data class Cat implements Animal { String name; public void eat() { System.out.println("猫正在吃猫粮"); } } @Data class Dog implements Animal { String name; public void eat() { System.out.println("狗正在吃狗粮"); } }5.迪米特法则(最少知道原则)
迪米特法则,也叫最少知道原则(封装)。一个类,对于其他类,要知道的越少越好,只和自己的朋友通信。哪些是自己的朋友?1、类中的字段2、方法的返回值 3、方法的参数 4、方法中实例化出的对象
反例:
/** * <p> * <p> * * @author xmp * @since 2020/9/18 */ public class demeterNegativeTest { public static void main(String[] args) { Person person = new Person(); person.shutdownComputer(); } } class Computer { public void saveData() { System.out.println("保存数据"); } public void killProcess() { System.out.println("关闭程序"); } public void closeScreen() { System.out.println("关闭屏幕"); } public void powerOff() { System.out.println("关闭电源"); } } class Person { private Computer computer = new Computer(); //Person对于Computer的细节知道的太多。对于person只需要知道关机键在哪里就行,不需要知道细节 //这种做法提高了代码的复杂度 public void shutdownComputer() { computer.saveData(); computer.killProcess(); computer.closeScreen(); computer.powerOff(); } }
正例:
/** * <p> * <p> * * @author xmp * @since 2020/9/18 */ public class DemeterPositiveTest { public static void main(String[] args) { Person person = new Person(); person.shutdownComputer(); } } class Computer { public void saveData() { System.out.println("保存数据"); } public void killProcess() { System.out.println("关闭程序"); } public void closeScreen() { System.out.println("关闭屏幕"); } public void powerOff() { System.out.println("关闭电源"); } public void shutdown() { saveData(); killProcess(); closeScreen(); powerOff(); } } class Person { private Computer computer = new Computer(); public void shutdownComputer() { computer.shutdown(); } }6.里氏替换原则
任何能使用父类对象的地方,都应该能透明地替换为子类对象。子类对象替换父类对象,语法不会报错,业务逻辑也不会出问题。
/** * 方法重写:在子类和父类中,出现返回类型相同、方法名相同、方法参数相同的方法时,构成方法重写 * 方法重写的两个限制:1.子类重写父类方法时,子类方法的修饰符不能比父类更严格 2.子类重写父类方法时,不能抛出比父类更多的异常 * 为什么要有这两个限制? 为了子类对象替换父类对象时,语法不报错,满足里氏替换原则。 */ class Fu { public void f1(){ } public void f2(){} } class Zi extends Fu{ //编译报错'f1()' in 'Zi' clashes with 'f1()' in 'Fu'; attempting to assign weaker access privileges ('private'); was 'public' private void f1() { } //编译报错'f2()' in 'Zi' clashes with 'f2()' in 'Fu'; overridden method does not throw 'java.lang.Exception' public void f2() throws Exception{ } }7.组合优于继承
类与类之间的关系有3种:
1.继承:子类继承父类
2.依赖: 一个类的对象作为另一个类的局部变量
3.关联:一个类的对象作为另一个类的字段(属性)
"关联"可以细分为:1.组合(关系强,鸟和翅膀) 2.聚合(关系弱,大雁和雁群)
组合优于继承中的组合没有分的那么细,其实指的的就是关联关系。
例A:
/** * <p> * 需求:制作一个集合,要求该集合能够记录曾经加过多少次元素。(不是统计某一时刻有多少元素) * <p> * * @author xmp * @since 2020/9/16 */ public class AppTest { public static void main(String[] args) { MySet set = new MySet(); Set set1 = new HashSet(); set1.add(4); set1.add(5); set.addAll(set1); System.out.println(set.getCount()); //想要的结果是2结果返回了4 //原因是addAll方法回调了add方法。所以这种代码没有完成需求 } } class MySet extends HashSet { private int count = 0; @Override public boolean add(Object o) { count++; return super.add(o); } @Override public boolean addAll(Collection c) { count += c.size(); return super.addAll(c); } public int getCount() { return count; } }
例B:
/** * <p> * 需求:制作一个集合,要求该集合能够记录曾经加过多少次元素。(不是统计某一时刻有多少元素) * 针对例A中出现的问题,addAll方法会回调add方法,只需要去掉子类中的addAll方法,不去重写父类的addAll方法即可, * 因为addAll会去回调add方法 * <p> * * @author xmp * @since 2020/9/16 */ public class AppTest { public static void main(String[] args) { MySet set = new MySet(); Set set1 = new HashSet(); set1.add(4); set1.add(5); set.addAll(set1); System.out.println(set.getCount()); //结果返回的2,已经满足了需求。 //问题:目前满足需求的前提是addAll方法会去回调add方法,但是万一jdk在未来的版本里面不再回调add方法,那么我们自定义的MySet这段代码就会被"撼动"! //例如:HashMap在JDK1.6、1.7、1.8中底层分别变换了3次! } } class MySet extends HashSet { private int count = 0; @Override public boolean add(Object o) { count++; return super.add(o); } public int getCount() { return count; } }
例C:
/** * <p> * 需求:制作一个集合,要求该集合能够记录曾经加过多少次元素。(不是统计某一时刻有多少元素) * 针对例B中出现的问题,MySet必需依赖一个事实:addAll方法必需回调add方法,但是JDK未来的版本无法做这个保证 * 修改代码如下:我们亲自重写addAll方法,保证一定会回调add方法 * <p> * * @author xmp * @since 2020/9/16 */ public class AppTest { public static void main(String[] args) { MySet set = new MySet(); Set set1 = new HashSet(); set1.add(4); set1.add(5); set.addAll(set1); System.out.println(set.getCount()); //结果:2 //问题1:如果在新的JDK版本中,突然多了一个添加元素到集合的方法:addSome,这个方法是我们始料未及的,我们的MySet根本没有重写addSome方法。 //这样在新版本中,我们的MySet也继承了addSome方法,当使用addSome方法添加元素时,就不会去统计元素添加的记录。 //问题2:我们重写了addAll和add方法,但是在HashSet的所有方法中,可能会有一些方法依赖于addAll和add方法。我们没头没脑地重写了别人的方法,就会导致其他依赖于这些方法的方法出现问题! } } class MySet extends HashSet { private int count = 0; @Override public boolean add(Object o) { count++; return super.add(o); } @Override public boolean addAll(Collection c) { boolean modified = false; for (Object e : c) if (add(e)) modified = true; return modified; } public int getCount() { return count; } }
例D:
/** * <p> * 需求:制作一个集合,要求该集合能够记录曾经加过多少次元素。(不是统计某一时刻有多少元素) * 针对例C中出现的问题 * 修改代码如下:1.我们不再重写addAll和add方法。2我们额外制作两个方法addAll2和add2方法,来替换addAll和add方法,而且还要在类的api文档里面说明方法怎么使用。 * <p> * * @author xmp * @since 2020/9/16 */ public class AppTest { public static void main(String[] args) { MySet set = new MySet(); Set set1 = new HashSet(); set1.add(4); set1.add(5); set.addAll2(set1); System.out.println(set.getCount()); //结果:2 //此时,代码勉强满足了需求 //问题1:用户必须看了api文档才能知道怎么使用addAll2和add2方法,而且方法名不能写错 //问题2:万一在将来的某个版本JDK中,恰恰也出现了addAll2和add2方法,那就没辙了 //难道不能用继承了吗? } } class MySet extends HashSet { private int count = 0; public boolean add2(Object o) { count++; return super.add(o); } public boolean addAll2(Collection c) { boolean modified = false; for (Object e : c) if (add2(e)) modified = true; return modified; } public int getCount() { return count; } }
例E:
/** * <p> * 需求:制作一个集合,要求该集合能够记录曾经加过多少次元素。(不是统计某一时刻有多少元素) * 针对例D中出现的问题 * 修改代码如下:1.我们的MySet不再继承HashSet 2.取而代之,我们让MySet和HashSet发生关联关系(组合) * <p> * * @author xmp * @since 2020/9/16 */ public class AppTest { public static void main(String[] args) { MySet set = new MySet(); Set set1 = new HashSet(); set1.add(4); set1.add(5); set.addAll(set1); System.out.println(set.getCount()); //结果:2 //此时,可以说一声:完美 //问题1:难道以后不能用继承了吗? //问题2:难道以后不能方法重写了吗? //如果父类的作者和子类的作者不是同一个人,就不要继承,那么父类的作者不知道未来的子类会重写哪些方法,那么子类的作者也不知道未来的父类会修改或新增哪些方法 //如果父类和子类的作者是同一个人,那么可以大胆的使用继承,如果我们仅仅为了复用代码而且使用继承,难免会出现"沟通"上的问题。 } } class MySet { private HashSet hashSet = new HashSet(); private int count = 0; public boolean add(Object o) { count++; return hashSet.add(o); } public boolean addAll(Collection c) { count += c.size(); return hashSet.addAll(c); } public int getCount() { return count; } }
标签: #java设计原则详解