设计模式七大设计原则

news2024/12/27 13:33:50

文章目录

  • 1、什么是设计模式
  • 2、单一职责原则
  • 3、开闭原则
  • 4、接口隔离原则
  • 5、依赖倒置原则
  • 6、迪米特法则(最少知道原则)
  • 7、里式替换原则
  • 8、组合优于继承

设计模式主要是为了满足一个字 ,这个字,可能是需求变更、可能是场景变更,但是运用好设计模式后我们写出的代码就能很好的应对不断变化的场景。

1、什么是设计模式

设计模式是前辈们不断总结、优化、打磨出来的设计方法,不同设计模式适用于不同的场景

但要明确一点,没有任何一种设计模式,能达到适用于所有场景的效果!

只有运用好设计原则和设计模式,才能让我们写出更加优秀的代码或者设计更好软件架构

设计模式有23种,其中每个设计模式又依赖于七大设计原则中的一个或多个

  • 单一职责原则
  • 开闭原则
  • 接口隔离原则
  • 里氏替换原则
  • 依赖倒置原则
  • 组合优于继承原则
  • 迪米特法则(最少知道原则)

下面我们详细聊聊七大设计原则

2、单一职责原则

单一职责是什么呢?

核心思想:每个方法、每个类、每个框架都只负责一件事情

举个栗子:

  • Math.round() ,只负责完成四舍五入的功能,其他的不管(方法)

  • Reader类,只负责读取文本文件(类)

  • Spring MVC,只负责简化MVC开发(框架)

单一职责讲究一个”“字,将功能尽可能的拆分,然后使用的时候进行组合

优点:

​ 1.代码重用性提高

​ 2.代码可读性提高,此时的代码,就像一个大纲一样

现在需求来了:统计一个文本文件中有多少个单词

我们先来看一个栗子

public class nagtive {
    public static void main(String[] args) {
        try{
            //统计一个文本文件中有多少个单词
            //Reader默认查询的码表是与操作系统一致的码表,我们的操作系统是中文的,所以Reader就会使用GBK码表
            //GBK码表一个汉字占2个字节 ,且汉字的两个字节都是以1开头,utf8码表一个汉字占3个字节
            //读取到记事本中的数字45489---> GBK --->北 --->unicode ---> 21271
            //总之一句话:字符流读取文件会查询码表,字节流不会查询码表
            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();
        }
    }
}

相信很多同学拿到需求上来就是梭哈搞定,同一个方法将所有的事做完了(让它去做文件读取,还让它去做内容分割)。这就违背了单一职责原则。

之前聊到设计模式讲究一个”“字,那现在需求变了,我们需要统计文本文件中有多少个句子,那我们的做法是什么呢?重新写一个方法,将读取文件的内容部分的代码复制粘贴过去?然后改一下分割条件?这样虽然能解决问题,但是你有没有发现代码变得很臃肿呀,这显然是不合理的。

那正确的做法是什么呢?应该是将读取文件内容部分封装成一个方法,将内容分割也封装成一个方法,然后根据需求进行组合

看下面这个栗子:

public class demo {
    //读取文件的内容
    public static StringBuilder loadFile(String path) throws IOException {

        Reader in = new FileReader(path);
        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[] getSplit(String regex, StringBuilder sb){
        return  sb.toString().split(regex);
    }
    //--------------------------------------------------------------------------
    //需求:
    //统计一个文本文件中有多少个单词
    public static Integer getWords() throws IOException {
        //读取文件的内容
        StringBuilder sb = loadFile("E:\\1.txt");
        //对内容进行分割
        String[] words = getSplit("[^a-zA-Z]+", sb);

        return words.length;
    }
    //统计一个文本文件中有多少个句子
    public static Integer getSentence() throws IOException {
        //读取文件的内容
        StringBuilder sb = loadFile("E:\\1.txt");
        //对内容进行分割
        String[] words = getSplit("[.,!?]", sb);

        return words.length;
    }
    public static void main(String[] args) throws IOException {
        System.out.println(getWords());
        System.out.println(getSentence());

    }
}

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

3、开闭原则

简单来说就是

对扩展开放,对修改关闭

在程序需要进行拓展的时候,不能去修改原有的代码。

举个栗子,我现在有一个刮胡刀,刮胡刀的功能应该就是刮胡子,但是我现在想要它拥有吹风机的能力

  • 违法开闭原则的做法是,把吹风机的功能加上了,可能就不能刮胡子了
  • 符合开闭原则的做法是,把吹风功能加上,且没有影响之前刮胡子的功能

例如我现在有一个商品类Goods,这个类之前有一个方法是获取它的价格,例如:

public class Goods {
    private BigDecimal price;
    
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    public BigDecimal getPrice() {
        return this.price;
    }
}

现在变化来了,当前商品需要打8折进行销售,不符合开闭原则的做法就是直接在原来的代码中进行修改

public BigDecimal getPrice() {
    // BigDecimal可以防止精度丢失
    return this.price.multiply(new BigDecimal("0.8"));
}

这样显然是不合理的,因为我们对源代码进行了修改,如果下次是打七折,那是不是又要去改源代码呢

正确的做法应该是写一个子类DiscountGoods来拓展父类的功能,再在子类上进行修改,这样就不会破坏父类的功能,又能满足需求

public class DiscountGoods extends Goods{
    @Override
    public BigDecimal getPrice() {
        return super.getPrice().multiply(new BigDecimal("0.8"));
    }
}

这就叫对扩展开发,对修改关闭。我们在用设计模式编码时应该时刻注意的是,改源码是一件非常危险的事情,因为一个功能并不是只有你在使用,很容易造成牵一发而动全身的效果

但是如果我们因为要遵守开闭原则,每次对功能进行修改的时候,都去新写一个类,这样的会很繁琐,所以我们的准则是:

  • 如果这个类是自己写的,自己修改不会影响该类在其他地方的效果(不会牵一发而动全身),那就可以随意修改
  • 如果这个类不是自己写的,自己不清楚修改后会带来什么样的影响,那就不要修改,要符合开闭原则

4、接口隔离原则

接口隔离原则也是满足一个字 ”“,将接口的功能尽可能的拆分

应该使用多个专门的接口,而不是使用单一的总接口

即客户端不应该依赖于那些它不需要的接口

举个栗子:现在设计一个动物的接口,统一动物的行为,可能会这样写

public interface Animal {
    void eat();
    void fiy(); 
    void swim(); 
}

这三个行为分别是 吃、飞和游泳,似乎并没有什么问题,但是动物这个接口太广了,并不是所有的动物都有着这三种行为

例如小狗的栗子:

public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("小狗啃骨头");
    }
    @Override
    public void swim() {
        System.out.println("小狗会狗刨");
    }
    @Override
    public void fly() {
        throw new UnsupportedOperationException("小狗不会飞,你行你来");
    }
}

小狗并不具备飞的属性

正确的做法是将动物这个总接口拆分成多个单独的小接口

interface Eatable{
    void eat();
}

interface Swimable{
    void swim();
}

interface Flyable{
    void fly();
}

再不断的组合,实现不同的接口

核心思想还是高内聚,低耦合,通过不断组合不可分割的功能完成最终需要的功能

我们改进一下小狗的栗子

public class Dog implements Eatable, Swimable {
    @Override
    public void eat() {
        System.out.println("小狗啃骨头");
    }
    @Override
    public void swim() {
        System.out.println("小狗会狗刨");
    }
}

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

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

5、依赖倒置原则

面向接口编程,依赖于抽象而不依赖于具体

  • 上层不应该依赖于下层
  • 它们都应该依赖于抽象

区分上下层的方法为:调用别的方法的就是上层,被调用的就是下层

举个栗子:人喂养动物

class Person {
    public void feed(Dog dog) {
        System.out.println("开始喂dog...");
    }
}

class Dog {
    public void eat() {
        System.out.println("狗啃骨头");
    }
}

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

上述代码好像并没有什么问题,但是设计模式是为了应对变化,现在变化来了,现在客户端Person不仅需要喂狗,还需要喂猫。

直接添加一个Cat

class Cat {
    public void eat() {
        System.out.println("小猫吃鱼");
    }
}
public class AppTest {
    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类可以喂猫不就好了?

class Person {
    public void feed(Dog dog) {
        System.out.println("开始喂dog...");
    }
    public void feed(Cat dog) {
        System.out.println("开始喂Cat...");
    }
}

好家伙,这是不是为了应对变化直接改源码了?首当其冲的就是破坏了开闭原则,其次如果每次要多喂养一种动物就要去重载一个方法,似乎并不合理。

每当一个新的类需要依赖时,就要重载一个方法,这里就违反了依赖倒置原则,每当下层发生改变时,上层要一起改变(下层多个猫,上层要重载喂猫),这样的设计没有拓展性我们不应该依赖于具体的类,而应该依赖于抽象的接口!

我们聊回来,猫和狗都属于什么?是动物,狗和猫只是动物的实现,人应该去喂养动物,而不是具体的实现,所以我们应该进行依赖倒置,依赖抽象不依赖实现,这里我们只需要依赖一个抽象的动物类或者接口即可

class Person {
    public void feed(Animal animal) {
        System.out.println("开始喂动物...");
    }
}

interface Animal {
    void eat();
}

class Dog implements Animal{
    @Override
    public void eat() {
        System.out.println("狗啃骨头");
    }
}

class Cat implements Animal{
    @Override
    public void eat() {
        System.out.println("小猫吃鱼");
    }
}

---------------------------------------------------
public class AppTest {
    public static void main(String[] args) {
        Person person = new Person();
        Dog dog = new Dog();
        Cat cat = new Cat();
        // 喂狗
        person.feed(dog);
        // 喂猫
        person.feed(cat);
    }
}

看一下类图的变化

image-20221003003659847

这里有读者可能有疑问了?为什么是依赖倒置呢?

看类图,之前箭头是向下的,依赖于具体实现;之后大家都指向抽象,面向抽象编程,这就是依赖倒置。

6、迪米特法则(最少知道原则)

一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立

一个类,对于其他类,要知道的越少越好,封装的思想,封装内部细节,向外暴露提供功能的接口

只和朋友通讯,朋友是指:

  • 类中的字段
  • 方法的参数
  • 方法的返回值
  • 方法中实例化出来的对象
  • 对象本身
  • 集合中的泛型

我们来看一个栗子:现在有一个电脑,需要关闭它

class Compute {
    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 {
    Compute compute = new Compute();
    public void shutDownCompute() {
        compute.saveData();
        compute.killProcess();
        compute.closeScreen();
        compute.powerOff();
    }
}

好像没有什么问题,

但对于用户来说,知道的细节太多了,要是不小心搞错了步骤,那岂不是玩完?所以他不想知道关闭电脑的具体步骤,只想按一下按钮(封装)就好了

我们改一下上面的代码:

class Compute {
    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 shutDownCompute() {
        this.saveData();
        this.killProcess();
        this.closeScreen();
        this.powerOff();
    }
}

class Person {
    Compute compute = new Compute();

    public void shutDown() {
        compute.shutDownCompute();
    }
}

那么对于朋友而言的最少知道原则是什么呢?

  • 如果对于作为返回类型、方法参数、成员属性、局部变量的类,不需要过多的封装,应该提供应有的细节,由调用者自己弄清楚细节并承担异常的后果,这样由我们直接创造的对象,我们就能把它称为我们的朋友

  • 但是如果这个对象不是我们自己获得的,而是由被人提供的,就不是朋友,即朋友的朋友并不是自己的朋友

public class AppTest {
    public void func() {
        AppBean appBean = BeanFactory.getAppBean();
        // 朋友的朋友就不是朋友了
        appBean.getStr();
    }

}

class BeanFactory {
    public static AppBean getAppBean() {
        return new AppBean();
    }
}

class AppBean {
    public String getStr() {
        return "";
    }
}

那么想要和这个AppBean做朋友该怎么办呢?比如给它转换成方法参数

public class AppTest {
    public void func() {
        AppBean appBean = BeanFactory.getAppBean();
        // 朋友的朋友就不是朋友了
        this.getStr(appBean);
    }
    /* 将朋友的朋友的细节转换为自己熟悉的方法 */
    public String getStr(AppBean appBean){
        return appBean.getStr();
    }
}

相信很多同学看到这里很谜,这不是制造了很多小方法吗?确实迪米特法则的缺点就是如此,在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的业务逻辑无关。所以在开发中适当的违反一下也是可以的。

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

  1. 优先考虑将一个类设置成不变类。
  2. 尽量降低一个类的访问权限。
  3. 谨慎使用Serializable
  4. 尽量降低成员的访问权限。

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

7、里式替换原则

任何能够使用父类对象的地方,都应该能透明的替换为子类

也就是说:子类对象能够随时随地替换父类对象,并且替换完之后,语法不会报错,业务逻辑也不会出现问题

我们先聊一下方法重写的定义:

  • 在子类和父类中,出现了返回类型相同、方法名相同、方法参数相同的方法时,构成了方法重写。

方法重写的两个限制:

  1. 子类重写父类的方法时,子类方法的访问修饰符不能比父类更严格
  2. 子类重写父类的方法时,子类方法不能抛出比父类更多的异常

为什么要有这两个限制呢?

就是为了保证代码符合里氏替换原则

举个栗子:
在这里插入图片描述

正常情况下,如果子类抛出的异常比父类少,父类在执行方法时就会进行catch,并且能够捕获子类中的异常,所以这样进行替换时,就不会影响代码的结构,做到透明、无感知

有很多的例子都可以用里式替换进行解释,著名的例子有:长方形正方形问题

接下来我们具体看看长方形正方形的问题,先来回顾下继承方面的知识

继承的作用:

  • 提高代码重用性
  • 多态的前提

两个类能发生继承关系的依据是什么?

  • 先看两个类有咩有” is a “ 关系
  • 在两个类有了 is a 关系之后,还要考虑子类对象在替换了父类对象之后,业务逻辑是否发生变化。如果变化,就不能发生继承关系

正方形和长方形是 is a 关系,那么我们能不能让正方形类直接去继承长方形类呢?

答案是不能,为什么呢?因为还要考虑具体的业务场景,看看在具体的业务场景下,正方形替换了长方形之后,业务逻辑是否变化

举个栗子:

public class AppTest {
    //长方形
    @Getter
    @Setter
    static class Rectangular {
        private Integer width;
        private Integer 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关系
  • 子类可以扩展父类的功能,但是不能改变父类原有的功能。

鸵鸟非鸟问题

  • 在我们看来,鸵鸟属于鸟科,但是现在有个需求是送信(飞鸽传书),这个业务场景能将鸵鸟(子类)替换为鸟(父类)吗?鸵鸟不会飞,所以这显然是不可以的。

8、组合优于继承

复用别人的代码时,不宜使用继承,应该使用组合。

  • 组合,是一种强关联关系,整体对象和局部对象的生命周期是一样的,类似于大雁和翅膀的关系
    • 整体对象负责局部对象的生命周期
    • 局部对象不能被其他对象共享;
    • 如果整体对象被销毁或破坏,那么局部对象也一定会被销毁或破坏
  • 聚和,它是一种弱关联,是 【整体和局部】之间的关系,且局部可以脱离整体独立存在,类似于雁群和其中一只大雁的关系
    • 代表局部的对象有可能会被多个代表整体的对象所共享,而且不一定会随着某个代表整体的对象被销毁或破坏而被销毁或破坏,甚至代表局部的对象的生命周期可以超越整体

总而言之,组合是值的关联(Aggregation by Value),而聚合是引用的关联(Aggregation by Reference)

实心菱形的是组合空心菱形的是聚和,如果不区分就用虚线指向,组合是作为成员变量作为另一个类的引用,聚和是作为形参或者局部变量作为另一个类的引用

image-20221003000126551

组合大家在平时编码的时候一定经常使用,举一个简单的例子,如果我们现在要有链表实现队列应该怎么做呢?队列的特点就是先进先出,完全可以用链表实现,我们可以用继承关系来做:

public class Queue <E> extends LinkedList<E> {
    /**
     * 入队
     */
    public void enQueue(E element){
        this.add(element);
    }

    /**
     * 出队
     */
    public E deQueue(){
        return this.remove(0);
    }

}

似乎并没有什么问题,队列类继承自链表类,并暴露自己提供给外界的方法,但是当我们调用这个Queue时就会发现问题:

image-20221003154018680

好家伙,我的Queue本来只需要入队和出队两个方法,但是居然有这么多细节的方法供我使用,这就违背了迪米特法则,一个类的内部实现应该不要提供给外界,只暴露该提供的方法,这就是继承的问题,继承复用破坏包装,因为继承将基类的实现都暴露给派生类

如果我们换成组合该怎么做呢?

public class Queue<E> {
    // 成员变量 -> 组合关系
    LinkedList<E> list = new LinkedList<>();
    /**
     * 入队
     */
    public void enQueue(E element) {
        list.add(element);
    }

    /**
     * 出队
     */
    public E deQueue() {
        return list.remove(0);
    }
}

所以如果我们仅仅只是为了复用代码,可以优先考虑组合,如果是为了实现多态,可以优先继承

我们也来看一个反例叭,其实在Java中有很多不合理的设计,例如Serializable接口,Date类等等,这里就讲一个java.util.Stack的糟糕设计

image-20221003160116593

点进源码中看我们发现,原来是继承了Vector类,让其拥有了链表的能力,看着这个兄弟设计模式也没学好

image-20221003160243437

官方也注意到了这个设计不合理的地方,推荐我们使用Deque来实现栈

image-20221003160720506

其实我们看完了这些设计原则,就会发现其实都是为了应对不断变化的,在看一些源码中,例如Spring的源码、dubbo的源码、netty的源码中也是非常严谨的遵守这些开发规范的。

本文部分内容参考了我老大的博文:设计模式学习(汇总版

大佬的文章写的太好了

关于设计模式,我们后面接着聊…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/484860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【VAR | 时间序列】以美国 GDP 和通货膨胀数据为例的VAR模型简单实战(含Python源代码)

以美国 GDP 和通货膨胀数据为例&#xff1a; 1. 数据集 下载数据我们需要从 FRED 数据库下载美国 GDP 和通货膨胀数据&#xff0c;并将它们存储在 CSV 文件中。可以在 FRED 网站&#xff08;https://fred.stlouisfed.org/&#xff09;搜索并下载需要的数据。在这里&#xff0…

非静压模型SWASH学习(7)——自制算例Lock-Exchange

自制算例Lock-Exchange 算例简介模型配置网格及参数设置网格与地形初始条件与边界条件物理参数设置数值求解方法模型输出计算时间 模拟结果 SWASH是由Delft大学开发&#xff0c;用于模拟非静压条件下的水动力/波浪运动的数值模型。 与模型原理相关的内容详见以下论文&#xff1…

Centos系统安装RabbitMQ消息中间件

记录一下在centos7.x下面安装RabbitMQ消息中间件 RabbitMQ是一个开源而且遵循 AMQP协议实现的基于 Erlang语言编写&#xff0c;因此安装RabbitMQ之前是需要部署安装Erlang环境的 先安装Erlang https://packagecloud.io/rabbitmq/ 点进去可以看到 因为使用的centos是7.x版本的…

内网渗透(六十二)之 NTLM Realy 攻击

NTLM Realy 攻击 NTLM Realy 攻击其实应该称为Net-NTLM Realy 攻击,它发生在NTLM认证的第三步,在Response 消息中存在Net-NTLM Hash,当攻击者获得了 Net-NTLM Hash 后,可以重放Net-NTLM Hash 进行中间人攻击。 NTLM Realy 流程如图所示,攻击者作为中间人在客户端和服务器…

asp.net基于web的音乐管理网站dzkf17A9程序

本系统主要包含了等系统用户管理、公告信息管理、音乐资讯管理、音乐类型管理多个功能模块。下面分别简单阐述一下这几个功能模块需求。 管理员的登录模块&#xff1a;管理员登录系统对本系统其他管理模块进行管理。 用户的登录模块&#xff1a;用户登录本系统&#xff0c;对个…

如何免费使用ChatGPT进行学术润色?你需要这些指令...

目录 1 ChatGPT4.0上线2 中科院ChatGPT学术版3 学术润色Prompts 1 ChatGPT4.0上线 2023年3月14日&#xff0c;OpenAI发布ChatGPT4.0。ChatGPT4.0比3.5更大&#xff0c;拥有更多的参数。这意味着它可以更好地捕捉和理解语言的复杂性和细微差别&#xff0c;而且ChatGPT4.0是多模…

【五一创作】《嵌入式系统》知识总结6:GPIO的结构和功能

GPIO的结构和功能 1.STM32提供通用的输入输出引脚&#xff08;GPIO&#xff09; • 输出高低电平控制外设 • 输入0、1检测外设状态 2.可配置为复用的输入输出引脚&#xff08;AFIO&#xff09; • 大多数GPIO都有默认的复用功能&#xff1a; 可作为片上外设&#xff08;如…

基于MobileNet的人脸表情识别系统(MATLAB GUI版+原理详解)

摘要&#xff1a;本篇博客介绍了基于MobileNet的人脸表情识别系统&#xff0c;支持图片识别、视频识别、摄像头识别等多种形式&#xff0c;通过GUI界面实现表情识别可视化展示。首先介绍了表情识别任务的背景与意义&#xff0c;总结近年来利用深度学习进行表情识别的相关技术和…

android基础知识

架构&#xff1a; 应用框架层&#xff08;Java API Framework&#xff09;所提供的主要组件&#xff1a; 名称功能描述Activity Manager&#xff08;活动管理器&#xff09;管理各个应用程序生命周期&#xff0c;以及常用的导航回退功能Location Manager&#xff08;位置管理器…

智能是逻辑吗?

智能是指人或机器能够理解、学习、推理、解决问题和适应环境的能力。而逻辑是一种推理方式&#xff0c;它是智能中的一部分&#xff0c;帮助我们正确地推理和理解信息。逻辑能够提高我们的思考能力、解决问题的能力和决策能力&#xff0c;但智能还包括其他方面&#xff0c;如感…

数据结构课程——第一次作业

T1:Gram_ham实现凸包算法&#xff1a; &#xff08;1&#xff09;思路&#xff1a; &#xff08;2&#xff09;代码&#xff1a; #include<iostream> #include<string> #include<vector> #include<algorithm> #include<stack>using namespace …

存储网络架构——DAS、NAS、SAN、分布式组网架构

目录 DAS直连式存储 NAS网络附加存储 SAN存储 存储区域网络 分布式存储组网 DAS直连式存储 DAS遇到的挑战 NAS网络附加存储 向主机提供文件服务&#xff1b;文件系统由存储设备维护&#xff0c;用户访问文件系统&#xff0c;不直接访问底层存储 拥有所有主机上文件与底层存储空…

图像复原与重建

文章目录 一、实验目的二、实验内容1. 噪声图像及其直方图。2. 空间噪声滤波器。3. 逆滤波。 一、实验目的 了解一些常用随机噪声的生成方法。掌握根据指定退化函数对图像进行退化的方法。掌握当模糊图像只存在噪声时的几种滤波复原方法。掌握当模糊图像同时存在线性退化和噪声…

OpenCV C++案例实战三十一《动态时钟》

OpenCV C案例实战三十一《动态时钟》 前言一、绘制表盘二、绘制刻线三、获取系统时间四、结果展示五、源码总结 前言 本案例将使用OpenCV C实现动态时钟效果。原理也很简单&#xff0c;主要分为绘制表盘、以及获取系统时间两步。 一、绘制表盘 首先为了效果显示美观一点&…

数据驱动测试、结果报告生成,Python接口自动化测试全方位解析

B站首推&#xff01;2023最详细自动化测试合集&#xff0c;小白皆可掌握&#xff0c;让测试变得简单、快捷、可靠https://www.bilibili.com/video/BV1ua4y1V7Db 目录 一、背景 二、准备工作 三、编写测试脚本 四、数据驱动测试 五、结果报告生成 六、总结 七、参考链接 …

线性表,顺序表,链表

线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列 线性表是一种在实际中广泛使 用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直线 …

阿里云对象存储OSS使用 HTTPS访问

阿里云对象存储OSS使用 HTTPS ​ 在部署项目的时候遇到了一个问题&#xff0c;就是https页面访问http资源报错的问题。 问题&#xff1a; 写了一个前端项目在云服务器部署&#xff0c;我的域名申请了ssl证书并在云服务器nginx部署&#xff0c;所以页面是https页面&#xff0c;但…

Ansible的脚本-playbook 剧本

目录 1.剧本&#xff08;playbook&#xff09; 1.playbook介绍 2. playbooks 的组成 3.案例&#xff1a;编写httpd的playbook 4.定义、引用变量 5.指定远程主机sudo切换用户 6.when条件判断 7.迭代 2.playbook的模块 1.Templates 模块 2.tags 模块 3.Roles 模块 1.…

TCP协议——这篇文章GET全

TCP协议文章目录 1. UDP和TCP协议的比较1.1 UDP协议1.2 TCP协议1.3 特点比较 2. TCP协议建立连接的三次握手3. TCP协议断开连接的四次挥手4. TCP协议的几个特性4.1 确认应答4.2 超时重传4.3 连接管理4.4 滑动窗口4.5 流量控制4.6 拥塞控制 1. UDP和TCP协议的比较 UDP和TCP作为…

多维时序 | MATLAB实现BP神经网络多变量时间序列预测(考虑历史特征的影响,多指标、多图输出)

多维时序 | MATLAB实现BP神经网络多变量时间序列预测(考虑历史特征的影响,多指标、多图输出) 目录 多维时序 | MATLAB实现BP神经网络多变量时间序列预测(考虑历史特征的影响,多指标、多图输出)预测效果基本介绍程序设计学习总结参考资料预测效果 基本介绍 MATLAB实现BP神经网…