七大设计原则
单一职责
单一职责就是每个方法负责一件事。降低耦合性,对方法进行封装。
public class Test2 {
public static String loadFile(String fileName) throws IOException {
// FileReader reader = new FileReader("D:\\Bilysen Java\\idea\\java设计模式\\src\\seven_principles\\data\\4.txt");
FileReader reader = new FileReader(fileName);
BufferedReader br = new BufferedReader(reader);
String line = null;
StringBuffer sb = new StringBuffer();
while ((line = br.readLine())!=null){
sb.append(line);
sb.append(" ");
System.out.println(line);
}
br.close();
reader.close();
return sb.toString();
}
public static int getWords(String s,String re){
int count;
String[] strings = s.split(re);
// for (String string : strings) {
// System.out.println(string+"~~~"+string.length());
// }
if(strings[strings.length-1].trim().length()==0){
count = strings.length-1;
// System.out.println(strings.length-1);
}else {
count = strings.length;
// System.out.println(strings.length);
}
return count;
}
public static void main(String[] args) throws IOException {
// 对这两处的代码进行了封装,使用者就直接调用其方法,不需要了解其细节,其每一方法都只负责一块的业务,降低耦合度
String s = loadFile("D:\\Bilysen Java\\idea\\java设计模式\\src\\seven_principles\\data\\3.txt");
// System.out.println(getWords(s, "[!.。?]+"));
System.out.println(getWords(s,"[^a-zA-Z]+"));
// String[] strings = sb.toString().split("[^a-zA-Z]+");
}
}
里氏替换原则
在了解里氏替换原则之前,我们需要了解一下重写和重载
重载是指在一个方法名中实现多可用性,其特点是方法名可以相同,其返回值或者参数不同。这样既可区分两个相同名字的方法。
重写是指子类对父类允许访问的方法中实现重写,是一个返回类型相同,方法名相同,方法参数相同的方法。旨在重写父类的方法,将父类此方法中的代码重写,实现自己想要的效果。
重写的规则:
- 参数列表与被重写方法的参数列表必须完全相同。
- 返回值可以不相同,但是必须是重写方法返回值的派生类(子类)
- 子类重写父类方法时,子类方法的访问修饰符不能比父类更严格。如父类方法的访问修饰符是public,子类不可以是protected
- 子类重写父类方法时,子类方法不能抛出比父类更多的异常
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明(被使用)。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 构造方法不能被重写。
里氏替换原则是指:子类可以扩展父类的功能,但不能改变父类原有的功能。
现在有一个需求:制作一个方法,接受一个长方形,来修改长方形的宽和长, 只要发现宽比长小,就让宽累加1,知道宽刚好超过长为止。
- 反例:
正方形和长方形有"is a"关系,看看特定的业务场景下,正方形替换长方形以后,业务逻辑是否变化。
但是以下例子证明,当此方法虽然接收的对象是长方形对象,但是由于正方形的对象继承了长方形类,会导致传入正方形类发生向上转型,导致结果是死循环。没有达到想要的效果。
class Utils{
static void transfrom(Rectangle r){
while (r.getWidth()<=r.getHeight()){
r.setWidth(r.getWidth()+1);
System.out.println(r.getWidth()+":"+r.getHeight());
}
}
}
class Rectangle{
private double width;
private double height;
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
class Square extends Rectangle{
private double side;
@Override
public double getWidth() {
return this.side;
}
@Override
public double getHeight() {
return this.side;
}
@Override
public void setWidth(double width) {
this.side = width;
}
@Override
public void setHeight(double height) {
this.side= height;
}
}
public class Test2 {
public static void main(String[] args) {
Rectangle rectangle = new Square();
rectangle.setWidth(12);
rectangle.setHeight(20);
Utils.transfrom(rectangle);
}
}
- 正例
// 要解决反例中的问题,就必须解除Square和Rectangle的继承关系
// 因为在这样的业务场景之下,不能把Square当做一个Rectangle类型!
// 也就是说,这里Square 和 Rectangle 没有 “is a”关系!
interface Quadrangle {
double getWidth();
double getLength();
}
class Rectangle implements Quadrangle {
private double width;
private double length;
public double getWidth() {
return width;
}
public double getLength() {
return length;
}
public void setWidth(double width) {
this.width = width;
}
public void setLength(double length) {
this.length = length;
}
}
class Square implements Quadrangle {
private double sideLength;
@Override
public double getWidth() {
return sideLength;
}
@Override
public double getLength() {
return sideLength;
}
public void setSideLength(double sideLength) {
this.sideLength = sideLength;
}
}
// 需求,制作一个方法,接受一个长方形,来修改长方形的宽和长,
// 只要发现宽比长小,就让宽累加1,知道宽刚好超过长为止。
public class Test {
public static void tranfrom(Rectangle r) {
while(r.getWidth() <= r.getLength()) {
r.setWidth(r.getWidth() + 1);
System.out.println("width:" + r.getWidth() + ", " + r.getLength());
}
}
public static void main(String[] args) {
Square s = new Square();
Rectangle r = new Rectangle();
tranfrom(r);
}
}
开闭原则
开闭原则是指对拓展开放,对修改关闭。这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。这是设计模式中最重要的一个原则,很多时候这个原则的必须遵循。
两个概念:作者和用户。作者是指这代代码的架构和编写的人。用户是指调用代码,直接使用。
开闭原则应该遵循应用场景去考虑,如果源代码就是你自己写的,而且需求是稳定的,那么,直接修改源代码也是一个简单的做法。
但当源代码是别人的代码或架构是,我们就要去符合开闭原则,防止破坏结构的完整性,导致不可预测的错误!
有一个业务,需要展示卖车的相关信息。可是过了段时间变化来了,公司希望搞大酬宾,对车辆进行打折。
反例:直接在car类中的setPrice中乘以相对应的折扣。这很明显违背了开闭原则,源代码可能不是我写的,其次这个标准的价格背后可能会联系着其他的功能和业务,直接在这里改很有可能会影响到其他业务的正常使用。
正例:创建一个DiscountCar 打折汽车的类,继承父类Car,重写setPrice这个方法。
public class Car {
private String name;
private double price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
public class DiscountCar extends Car{
@Override
public void setPrice(double price) {
super.setPrice(price*0.8);
}
}
接口隔离原则
设计接口时,接口应该抽象化且有意义的。
客户端不应该依赖那些它不需要的接口。否则接口过于臃肿。
一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。
依赖倒置原则
代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。通过面向接口编程,抽象不应该依赖于细节,细节应该依赖于抽象。
有一个业务,有个人养了一只狗,需要进行喂狗。可是变化来了,人又养了一只猫,需要进行喂猫的操作。
反例:直接在People中重载feed的方法,很显然违背了开闭原则。一有什么变化都需要去改变上层。
public class test1 {
//通过重载来添加但是违背了开闭原则
static class People{
public void feed(Cat cat){
cat.feed();
}
public void feed(Dog dog){
dog.feed();
}
}
/*=========================增加业务需求===========================================*/
static class Cat{
public void feed(){
System.out.println("猫吃鱼");
}
}
static class Dog{
public void feed(){
System.out.println("狗吃骨头");
}
}
public static void main(String[] args) {
People people = new People();
Cat cat = new Cat();
people.feed(cat);
Dog dog = new Dog();
people.feed(dog);
}
}
此时的uml图是这样的,上层直接依赖了具体类,这导致了每当业务发生改变都需要在源代码中进行修改。
正例:作者创建一个接口,接口中写实现方法。当用户想调用就先实现接口,实现接口中的方法。这样就能解决开闭原则的问题。上层依赖的是接口,具体类实现接口。
public class test1 {
static class People{
public void feed(Animal animal){
animal.feed();
}
}
interface Animal{
void feed();
}
/*=========================增加业务需求===========================================*/
static class Cat implements Animal{
public void feed(){
System.out.println("猫吃鱼");
}
}
static class Dog implements Animal{
public void feed(){
System.out.println("狗吃骨头");
}
}
public static void main(String[] args) {
People people = new People();
people.feed(new Cat());
people.feed(new Dog());
}
}
uml图
迪米特法则
迪米特法则又称最少知道原则。一个类对于其他类,要知道越少越好。
有一个业务,关闭计算机
反例:Person类需要知道的太多了,关闭计算机还要清楚关机的步骤。
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 c;
//用户对于计算机的细节知道太多了
public void shutdown(){
c.saveData();
c.killProcess();
c.closeScreen();
c.powerOff();
}
}
正例:提供一个public方法,将关机的过程封装,用户知道哪个方法是关机即可。
class Computer{
private void saveData(){
System.out.println("保存数据");
}
private void killProcess(){
System.out.println("关闭程序");
}
private void closeScreen(){
System.out.println("关闭屏幕");
}
private void powerOff(){
System.out.println("断电");
}
public void shutdown(){
saveData();
killProcess();
closeScreen();
powerOff();
}
}
class Person{
private Computer c;
//用户对于计算机的细节知道太多了
public void shutdown(){
c.shutdown();
}
}
组合优于继承
在学习组合优于继承之前,我们必须先弄清楚几个概念。
- 继承:一个类继承另外一个类
- 依赖:一个类的对象作为另外类方法的局部变量
- 关联:一个类知道另一个类的属性和方法
有一个业务,制作一个集合,要求该集合至今加过多少元素
a包:继承HashSet,重写add()和addAll()方法。
问题:addAll中回调了add方法,导致数据错误
class MySet extends HashSet{
private int count;
public int getCount() {
return count;
}
@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 class Test {
public static void main(String[] args) {
MySet mySet = new MySet();
mySet.add(1);
mySet.add(1);
mySet.add(1);
HashSet hashSet = new HashSet();
hashSet.add("1");
hashSet.add("2");
mySet.addAll(hashSet);
System.out.println(mySet.getCount());
}
}
b包:针对a包的问题,因为addAll底层回调了add方法,解决:不重写addAll方法
问题:在将来的jdk版本中,底层addAll不在调用add方法了,我们所写的方法将会出错。
class MySet extends HashSet{
private int count;
public int getCount() {
return count;
}
@Override
public boolean add(Object o) {
count++;
return super.add(o);
}
}
public class Test {
public static void main(String[] args) {
MySet mySet = new MySet();
mySet.add(1);
mySet.add(1);
mySet.add(1);
HashSet hashSet = new HashSet();
hashSet.add("1");
hashSet.add("2");
mySet.addAll(hashSet);
System.out.println(mySet.getCount());
}
}
c包:我们自己亲自重写addAll
问题:
1.如果在新版本的jdk中多了一个新的addSome()方法,这样我们就必需重写此方法。
2.重写hashset方法中,难免会有其他方法依赖于hashset中的方法,我们重写难免会有作者所写结构崩溃的风险。
class MySet extends HashSet{
private int count;
public int getCount() {
return count;
}
@Override
public boolean add(Object o) {
count++;
return super.add(o);
}
@Override
public boolean addAll(Collection c) {
boolean flag = false;
for (Object o : c) {
if (add(o)){
flag = true;
}
}
return flag;
}
}
public class Test {
public static void main(String[] args) {
MySet mySet = new MySet();
mySet.add(1);
mySet.add(1);
mySet.add(1);
HashSet hashSet = new HashSet();
hashSet.add("1");
hashSet.add("2");
mySet.addAll(hashSet);
System.out.println(mySet.getCount());
}
}
d包:当源代码作者不是我们本人时,我们要采用组合的方式。我们类中的add和addAll方法跟HashSet中的add和addAll方法的不存在关系,也能解决这个问题
class MySet{
//组合
private Set set = new HashSet();
private int count;
public int getCount() {
return count;
}
public boolean add(int i) {
count++;
return set.add(i);
}
public boolean addAll(Collection c) {
count=count+c.size();
return set.addAll(c);
}
}
public class Test {
public static void main(String[] args) {
MySet mySet = new MySet();
mySet.add(1);
mySet.add(1);
mySet.add(1);
HashSet hashSet = new HashSet();
hashSet.add("1");
hashSet.add("2");
mySet.addAll(hashSet);
System.out.println(mySet.getCount());
}
}