设计模式 #1(7大设计原则)

简述:单个类,单个方法或者单个框架只完成某一特定功能。

需求:统计文本文件中有多少个单词。

反例:

Copy
public class nagtive {
public static void main(String[] args) {
try{
//读取文件的内容
Reader in = new FileReader(“E:\1.txt”);
BufferedReader bufferedReader = new BufferedReader(in);

        String line = null;
        StringBuilder sb = new StringBuilder("");

        while((line =bufferedReader.readLine()) != null){
            sb.append(line);
            sb.append(" ");
        }

        //对内容进行分割
        String[] words = sb.toString().split("[^a-zA-Z]+");
        System.out.println(words.length);

        bufferedReader.close();

    } catch (IOException e) {
        e.printStackTrace();
    }
}

}
上面代码违反单一职责原则,同一个方法我们让它去做文件读取,还让他去做内容分割;当有需求变更(需要更换加载文件,统计文本文件中有多少个句子)时,我们需要重写整个方法。

正例:

Copy
public class postive {

public static StringBuilder loadFile(String fileLocation) throws IOException {
  
        //读取文件的内容
        Reader in = new FileReader("E:\\1.txt");
        BufferedReader bufferedReader = new BufferedReader(in);

        String line = null;
        StringBuilder sb = new StringBuilder("");

        while ((line = bufferedReader.readLine()) != null) {
            sb.append(line);
            sb.append(" ");
        }
        
        bufferedReader.close();
        return sb;
}

public static String[] getWords(String regex, StringBuilder sb){
    //对内容进行分割
    return  sb.toString().split(regex);
}

public static void main(String[] args) throws IOException {
    
        //读取文件的内容
        StringBuilder sb = loadFile("E:\\1.txt");
        
        //对内容进行分割
        String[] words = getWords("[^a-zA-Z]+", sb);
        
        System.out.println(words.length);
}

}
遵守单一原则,可以给我们带来的好处是,提高了代码的可重用性,同时还让得到的数据不再有耦合,可以用来完成我们的个性化需求。

开闭原则#
简述:对扩展(新功能)开放,对修改(旧功能)关闭

实体类设计:

Copy
public class Pen {
private String prod_name;
private String prod_origin;
private float prod_price;

public String getProd_name() {
    return prod_name;
}

public void setProd_name(String prod_name) {
    this.prod_name = prod_name;
}

public String getProd_origin() {
    return prod_origin;
}

public void setProd_origin(String prod_origin) {
    this.prod_origin = prod_origin;
}

public float getProd_price() {
    return prod_price;
}

public void setProd_price(float prod_price) {
    this.prod_price = prod_price;
}

@Override
public String toString() {
    return "Pen{" +
            "prod_name='" + prod_name + '\'' +
            ", prod_origin='" + prod_origin + '\'' +
            ", prod_price=" + prod_price +
            '}';
}

}
Copy
public static void main(String[] args) {
//输入商品信息
Pen redPen = new Pen();
redPen.setProd_name(“英雄牌钢笔”);
redPen.setProd_origin(“厂里”);
redPen.setProd_price(15.5f);
//输出商品信息
System.out.println(redPen);
}
需求:商品搞活动,打折8折销售。

反例:在实体类的源代码,修改 setProd_price方法

Copy
public void setProd_price(float prod_price) {
this.prod_price = prod_price * 0.8f;
}
违反了开闭原则,在源代码中修改,对显示原价这一功能进行了修改。

在开发时,我们应该,必须去考虑可能会变化的需求,属性在任何时候都可能发生改变,对于需求的变化,在要求遵守开闭原则的前提下,我们应该在开发中去进行扩展,而不是修改源代码。

正例:

Copy
public class discountPen extends Pen{
//用重写方法设置价格
@Override
public void setProd_price(float prod_price) {
super.setProd_price(prod_price * 0.8f);
}
}
Copy
public class postive {
public static void main(String[] args) {
//输入商品信息,向上转型调用重写方法设置价格
Pen redPen = new discountPen();
redPen.setProd_name(“英雄牌钢笔”);
redPen.setProd_origin(“厂里”);
redPen.setProd_price(15.5f);
//输出商品信息
System.out.println(redPen);
}
}
开闭原则并不是必须要一味地死守,需要结合开发场景进行使用,如果需要修改的源代码是自己写的,修改之后去完成需求,当然是简单快速的;但是如果源代码是别人写的,或者是别人的架构,修改是存在巨大风险的,这时候应该去遵守开闭原则,防止破坏结构的完整性。

接口隔离原则#
简述:设计接口的时候,接口的抽象应是具有特定意义的。需要设计出的是一个内聚的、职责单一的接口。“使用多个专门接口总比使用单一的总接口好。”这一原则不提倡设计出具有“普遍”意义的接口。

反例:动物接口中并不是所有动物都需要的。

Copy
public interface Animal {
void eat();
void fiy(); //泥鳅:你来飞?
void swim(); // 大雕:你来游?
}
Copy
class Bird implements Animal {
@Override
public void eat() {
System.out.println(“用嘴巴吃”);
}

@Override
public void fiy() {
    System.out.println("用翅膀飞");
}

@Override
public void swim() {
//我是大雕不会游泳
}

}
接口中的 swim()方法在实际开发中,并不适用于该类。

正例:接口抽象出同一层级的特定意义,提供给需要的类去实现。

Copy
public interface Fly {
void fly();
}

public interface Eat {
void eat();
}

public interface Swim {
void swim();
}
Copy
public class Bird_02 implements Fly,Eat{
@Override
public void eat() {
System.out.println(“用嘴巴吃”);
}

@Override
public void fly() {
    System.out.println("用翅膀飞");
}

//我是大雕不会游泳

}
客户端依赖的接口中不应该存在他所不需要的方法。

如果某一接口太大导致这一情况发生,应该分割这一接口,使用接口的客户端只需要知道他需要使用的接口及该接口中的方法即可。

依赖倒置原则#
简述:上层不能依赖于下层,他们都应该依赖于抽象。

需求:人喂养动物

反例:

Copy
public class negtive {

static class Person {
    public void feed(Dog dog){
        dog.eat();
    }
}

static class Dog {
    public void eat() {
        System.out.println("主人喂我了。汪汪汪...");
    }
}

public static void main(String[] args) {
    Person person= new Person();
    Dog dog = new Dog();
    person.feed(dog);
}

}
image-20200912204644913

这时候,Person内部的feed方法依赖于Dog,是上层方法中又依赖于下层的类。(人竟然依赖于一条狗?这算骂人吗?)

当有需求变更,人的宠物不止有狗狗,还可以是猫等等,这时候需要修改上层类,这带来的是重用性的问题,同时还违反上面提到的开闭原则。

正例:

image-20200912204707141

Copy
public class postive {
static class Person {
public void feed(Animal animal){
animal.eat();
}
}

interface Animal{
    public void eat();
}

static class Dog implements Animal{
    public void eat() {
        System.out.println("我是狗狗,主人喂我了。汪汪汪...");
    }
}

static class Cat implements Animal{
    public void eat() {
        System.out.println("我是猫咪,主人也喂我了。(我为什么要说也?)喵喵喵...");
    }
}

public static void main(String[] args) {
    Person person= new Person();
    Dog dog = new Dog();
    Cat cat = new Cat();
    person.feed(dog);
    person.feed(cat);
}

}
这时候,Person内部的feed方法不在依赖于依赖于Dog或者Cat,而是不管是Person,还是Dog或者Cat,他们都依赖与Animal这一抽象类,都依赖于抽象类。

这时候,不管是曾经的上层代码,还是曾经的下层代码,都不会因为需求而改变。

依赖倒转原则就是指:代码要依赖于抽象的类,而不要依赖于具体的类;要针对接口或抽象类编程,而不是针对具体类编程。通过面向接口编程,抽象不应该依赖于细节,细节应该依赖于抽象。

迪米特法则(最少知道原则)#
简述:一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。

反例:

Copy
public class negtive {
class Computer{
public void closeFile(){
System.out.println(“关闭文件”);
}
public void closeScreen(){
System.out.println(“关闭屏幕”);
}
public void powerOff(){
System.out.println(“断电”);
}
}

class Person{
    private Computer computer;

    public void offComputer(){
        computer.closeFile();
        computer.closeScreen();
        computer.powerOff();
    }
}

}
这时候,Person 知道了 Computer的很多细节,对于用户来说不够友好,而且,用户还可能会调用错误,先断电,再保存文件,显然不符合逻辑,会导致文件出现未保存的错误。

其实对于用户来说,知道进行关机就行了。

正例:封装细节

Copy
public class postive {
class Computer{
public void closeFile(){
System.out.println(“关闭文件”);
}
public void closeScreen(){
System.out.println(“关闭屏幕”);
}
public void powerOff(){
System.out.println(“断电”);
}

    public void turnOff(){  //封装细节
        this.closeFile();
        this.closeScreen();
        this.powerOff();
    }
}

class Person{
    private Computer computer;

    public void offComputer(){
        computer.turnOff();
    }
}

}
前面说的,只和朋友通信,不和陌生人说话。先来明确一下什么才叫做朋友:

什么是朋友?#
类中的字段
方法的返回值
方法的参数
方法中的实例对象
对象本身
集合中的泛型
总的来说,只要在自身内定义的就是朋友,通过其他方法得到的都只是朋友的朋友;

但是,朋友的朋友不是我的朋友。

举个反例:

Copy
public class negtive {

 class Market{
    private  Computer computer;
    public Computer getComputer(){
        return this.computer;
    }
}

static class Computer{
    public  void  closeFile(){
        System.out.println("关闭文件");
    }
    public  void  closeScreen(){
        System.out.println("关闭屏幕");
    }
    public  void  powerOff(){
        System.out.println("断电");
    }
}

class Person{
    private Market market;

    Computer computer =market.getComputer(); 
    // //此时的 computer 并不是 Person 的朋友,只是 Market 的朋友。
}

}
在实际开发中,要完全符合迪米特法则,也会有缺点:

在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的业务逻辑无关。

遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。

因此,前人总结出,一些方法论以供我们参考:

优先考虑将一个类设置成不变类。

尽量降低一个类的访问权限。

谨慎使用Serializable。

尽量降低成员的访问权限。

虽然规矩很多,但是理论需要深刻理解,实战需要经验积累。路还很长。

里氏替换原则#
简述:任何能使用父类对象的地方,都应该能透明地替换为子类对象。

需求:将长方形的宽改成比长大 1 。

反例:在父类Rectangular下,业务场景符合逻辑。现有子类Square,替换后如何。

Copy
public class negtive {
static class Rectangular {
private Integer width;
private Integer length;

    public Integer getWidth() {
        return width;
    }

    public void setWidth(Integer width) {
        this.width = width;
    }

    public Integer getLength() {
        return length;
    }

    public void setLength(Integer length) {
        this.length = length;
    }

}

static class Square extends Rectangular {
    private Integer sideWidth;

    @Override
    public Integer getWidth() {
        return sideWidth;
    }

    @Override
    public void setWidth(Integer width) {
        this.sideWidth = width;
    }

    @Override
    public Integer getLength() {
        return sideWidth;
    }

    @Override
    public void setLength(Integer length) {
        this.sideWidth = length;
    }
}


static class Utils{
    public static void transform(Rectangular graph){
        while ( graph.getWidth() <= graph.getLength() ){
            graph.setWidth(graph.getWidth() + 1);
            System.out.println("长:"+graph.getLength()+" : " +
                    "宽:"+graph.getWidth());
        }
    }
}

public static void main(String[] args) {
// Rectangular graph = new Rectangular();
    Rectangular graph = new Square();
    graph.setWidth(20);
   graph.setLength(30);

    Utils.transform(graph);
}

}
替换后运行将是无限死循环。

要知道,在向上转型的时候,方法的调用只和new的对象有关,才会造成不同的结果。在使用场景下,需要考虑替换后业务逻辑是否受影响。

由此引出里氏替换原则的使用需要考虑的条件:

是否有is-a关系
子类可以扩展父类的功能,但是不能改变父类原有的功能。
这样的反例还有很多,如:鸵鸟非鸟,还有咱们老祖宗早就说过的的春秋战国时期–白马非马说,都是一个道理。

组合优于继承#
简述:复用别人的代码时,不宜使用继承,应该使用组合。

需求:制作一个组合,该集合能够记录下曾经添加过多少元素。(不只是统计某一时刻)

反例 #1:

Copy
public class negtive_1 {

static class MySet extends HashSet{
    private int count = 0;

    public int getCount() {
        return count;
    }

    @Override
    public boolean add(Object o) {
        count++;
        return super.add(o);
    }
}

public static void main(String[] args) {
    MySet mySet = new MySet();
    mySet.add("111111");
    mySet.add("22222222222222");
    mySet.add("2333");


    Set hashSet = new HashSet();
    hashSet.add("集合+11111");
    hashSet.add("集合+22222222");
    hashSet.add("集合+233333");
    mySet.addAll(hashSet);

    System.out.println(mySet.getCount());
}

}
看似解决了需求,add 方法可以成功将count进行自加, addAll方法通过方法内调用add,可以成功将count进行增加操作。

缺陷:JDK版本如果未来进行更新,addAll方法不再通过方法内调用add,那么当调用addAll进行集合添加元素时,count将不无从进行自加。需求也将无法满足。

HashMap 就在 1.6 1.7 1.8就分别更新了三次。

反例 #2:

Copy
public class negtive_2 {

static class MySet extends HashSet{
    private int count = 0;

    public int getCount() {
        return count;
    }

    @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 static void main(String[] args) {
    MySet mySet = new MySet();
    mySet.add("111111");
    mySet.add("22222222222222");
    mySet.add("2333");


    Set hashSet = new HashSet();
    hashSet.add("集合+11111");
    hashSet.add("集合+22222222");
    hashSet.add("集合+233333");
    mySet.addAll(hashSet);

    System.out.println(mySet.getCount());
}

}
亲自再重写addAll方法,确保addAll方法一定能调用到add方法,也就能够对 count进行增加操作。

但是,问题还是有的:

缺陷:

如果未来,HashSet新增了一个addSome方法进行元素的添加,那就白给了。
重写了addAll、add这两个方法,如果JDK中其他类的某些方法依赖于HashMap中的这两个方法,那么JDK中其他类依赖于HashMap中的这两个方法的某些方法就会有出错、崩溃等风险。
这时候,可以得出一些结论:

当我们不属于继承父类的开发团队时,是没办法保证父类代码不会被修改,或者修改时一定被通知到,这时候,就可能会出现需求满足有缺陷的情况。所以,但我们去复用父类的代码时,避免去重写或者新建方法,这样可以防止源代码结构发生改变带来的打击。

也就是说,我们在重用代码时,应该是组合优于继承。

正例:

Copy
public class postive {

class MySet{
    private HashSet hashSet;

    private int count = 0;

    public int getCount() {
        return count;
    }

    public boolean add(Object o) {
        count++;
        return hashSet.add(o);
    }

    public boolean addAll(Collection c) {
        count += c.size();
        return hashSet.addAll(c);
    }
}

public static void main(String[] args) {
    negtive_2.MySet mySet = new negtive_2.MySet();
    mySet.add("111111");
    mySet.add("22222222222222");
    mySet.add("2333");


    Set hashSet = new HashSet();
    hashSet.add("集合+11111");
    hashSet.add("集合+22222222");
    hashSet.add("集合+233333");

    mySet.addAll(hashSet);

    System.out.println(mySet.getCount());
}

}
亚马逊测评 www.yisuping.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
众所周知,人工智能是当前最热门的话题之一, 计算机技术与互联网技术的快速发展更是将对人工智能的研究推向一个新的高潮。 人工智能是研究模拟和扩展人类智能的理论与方法及其应用的一门新兴技术科学。 作为人工智能核心研究领域之一的机器学习, 其研究动机是为了使计算机系统具有人的学习能力以实现人工智能。 那么, 什么是机器学习呢? 机器学习 (Machine Learning) 是对研究问题进行模型假设,利用计算机从训练数据中学习得到模型参数,并最终对数据进行预测和分析的一门学科。 机器学习的用途 机器学习是一种通用的数据处理技术,其包含了大量的学习算法。不同的学习算法在不同的行业及应用中能够表现出不同的性能和优势。目前,机器学习已成功地应用于下列领域: 互联网领域----语音识别、搜索引擎、语言翻译、垃圾邮件过滤、自然语言处理等 生物领域----基因序列分析、DNA 序列预测、蛋白质结构预测等 自动化领域----人脸识别、无人驾驶技术、图像处理、信号处理等 金融领域----证券市场分析、信用卡欺诈检测等 医学领域----疾病鉴别/诊断、流行病爆发预测等 刑侦领域----潜在犯罪识别与预测、模拟人工智能侦探等 新闻领域----新闻推荐系统等 游戏领域----游戏战略规划等 从上述所列举的应用可知,机器学习正在成为各行各业都会经常使用到的分析工具,尤其是在各领域数据量爆炸的今天,各行业都希望通过数据处理与分析手段,得到数据中有价值的信息,以便明确客户的需求和指引企业的发展。
众所周知,人工智能是当前最热门的话题之一, 计算机技术与互联网技术的快速发展更是将对人工智能的研究推向一个新的高潮。 人工智能是研究模拟和扩展人类智能的理论与方法及其应用的一门新兴技术科学。 作为人工智能核心研究领域之一的机器学习, 其研究动机是为了使计算机系统具有人的学习能力以实现人工智能。 那么, 什么是机器学习呢? 机器学习 (Machine Learning) 是对研究问题进行模型假设,利用计算机从训练数据中学习得到模型参数,并最终对数据进行预测和分析的一门学科。 机器学习的用途 机器学习是一种通用的数据处理技术,其包含了大量的学习算法。不同的学习算法在不同的行业及应用中能够表现出不同的性能和优势。目前,机器学习已成功地应用于下列领域: 互联网领域----语音识别、搜索引擎、语言翻译、垃圾邮件过滤、自然语言处理等 生物领域----基因序列分析、DNA 序列预测、蛋白质结构预测等 自动化领域----人脸识别、无人驾驶技术、图像处理、信号处理等 金融领域----证券市场分析、信用卡欺诈检测等 医学领域----疾病鉴别/诊断、流行病爆发预测等 刑侦领域----潜在犯罪识别与预测、模拟人工智能侦探等 新闻领域----新闻推荐系统等 游戏领域----游戏战略规划等 从上述所列举的应用可知,机器学习正在成为各行各业都会经常使用到的分析工具,尤其是在各领域数据量爆炸的今天,各行业都希望通过数据处理与分析手段,得到数据中有价值的信息,以便明确客户的需求和指引企业的发展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值