设计模式&&Java 新特性

818 阅读9分钟

设计原则

开发流程
  • 需求分析文档 - 分析客户的需求
  • 概要设计文档 - 对大概的要点进行设计
    • 如采用什么样的架构
    • 项目需要分成多少个模块,每个模块之间是什么关系
  • 详细设计文档 - UML 图,定义出每个接口和类
  • 编码和测试
  • 安装和调试
  • 维护和升级
设计原则
  • 开闭原则
    • 修改关闭
    • 扩展开放 - 如通过继承形式,不要改变源代码
  • 里氏代换原则
    • 任何父类出现的地方,子类一定可以出现,多使用多态。
  • 依赖倒转原则
    • 尽量多依赖接口和抽象类而不是具体的实现类,因为接口和抽象类可以对子类具有强制性和规范性。
  • 接口隔离原则 - 是上面原则的补充
    • 尽量依赖小接口,不要继承大接口,避免接口的污染,降低类之间的耦合。
    • 如 Animal 抽象类定义了 run 和 fly 抽象方法,Dog 实际上不应该继承该类,而是继承RunAnimal 类。
  • 迪米特原则(最少知道原则)
    • 模块与模块间尽量少发生相互作用,使系统功能模块相对独立。
    • 高内聚、低耦合。
  • 合成复用原则
    • 尽量多使用聚合的方式,而不是继承的方式。
    • 如 Stack 继承了 List,导致它还可以使用 insert 方法。
设计模式
  • 被反复使用,被多数人知晓的,经过分类编目的,代码设计经验的总结。
  • 就是一种用于固定场合的固定套路。
    • 创建型模式 - 单例设计模式、工厂方法模式、抽象工厂模式。
    • 结构型模式 - 装饰器模式、代理模式
    • 行为型模式 - 模板设计模式
单例设计模式

/**
 * @author timevaeless
 * @version 1.0
 * @date 2020/9/2 6:29 PM
 */
public class Singleton {

    // 1.定义 instance
    // 2.私有化成员方法
    // 3.提供 静态的 getInstance 方法

    // ①懒汉式存在什么问题?
    // 当两个线程同时第一次调用 getInstance 方法,可能第一个线程正在new 实例但还没完成实例化,所以 instance 还没有被赋值,
    // 这时候第二个线程也执行到 if 语句那,也会条件成立,进入 new 该实例,这样会存在两个实例,就并不是单例了。

    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {

        if (instance == null) { /*只有第一次实例化时才需要加锁,所以再判断一下*/

            synchronized (Singleton.class) {

                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}
工厂模式

/**
 * @author timevaeless
 * @version 1.0
 * @date 2020/9/2 6:49 PM
 */
public class SendFactory {

    // ①工厂模式有什么优势?
    // - 可扩展、可维护
    // - 尤其在创建大量对象的情况下

    // ②工厂模式适用哪些场合?
    // 凡是出现大量的产品需要创建,且具有相同接口时。

    // ③静态工厂模式有什么问题?
    // 因为当新的需求来到时,需要在生产方法里添加代码,违背了开闭原则。为了不违背,就引出了抽象工厂模式。
    // 即定义一个接口,发短信和发邮件的类实现该接口,即实现 SmsSendFactory 类和 MailSendFactory 类。


    /**
     * 普通工厂模式
     * 缺点:字符串传错了,会导致空指针异常
     *
     * @param type
     * @return
     */
    public static Sender produce(String type) {

        if ("MailSender".equals(type)) {
            return new MailSender();
        } else if ("SmsSender".equals(type)) {
            return new SmsSender();
        }

        return null;
    }


    /**
     * 多个工厂方法模式 - 解决空指针异常问题
     *
     * @return
     */
    public static Sender produceSms() {

        return new SmsSender();
    }

    public static Sender produceMail() {

        return new MailSender();
    }


    public static void main(String[] args) {

        Sender sender = SendFactory.produce("MailSender");

        if (sender != null) {
            sender.send();
        }

        Provider provider = new SmsSendFactory();
        sender = provider.produce();
        sender.send();

        provider = new MailSendFactory();
        sender = provider.produce();
        sender.send();
    }
}


interface Provider {
    Sender produce();
}

class SmsSendFactory implements Provider {

    @Override
    public Sender produce() {
        return new SmsSender();
    }
}

class MailSendFactory implements Provider {

    @Override
    public Sender produce() {
        return new MailSender();
    }
}

/**
 * @author timevaeless
 * @version 1.0
 * @date 2020/9/2 6:49 PM
 */
public interface Sender {

    void send();
}

/**
 * @author timevaeless
 * @version 1.0
 * @date 2020/9/2 6:50 PM
 */
public class MailSender implements Sender {
    @Override
    public void send() {
        System.out.println("正在发送邮件...");
    }
}

/**
 * @author timevaeless
 * @version 1.0
 * @date 2020/9/2 6:50 PM
 */
public class SmsSender implements Sender {
    @Override
    public void send() {
        System.out.println("正在发生短信...");
    }
}


装饰器模式

/**
 * @author timevaeless
 * @version 1.0
 * @date 2020/9/2 7:34 PM
 */

/**
 * ①装饰者模式的作用是什么?
 * - 给原来的对象动态的增加一些新功能。(动态是相比继承而言)
 */


interface Sourceable {

    void method();
}

class Source implements Sourceable {

    @Override
    public void method() {
        System.out.println("素颜示众");
    }
}

public class Decorator implements Sourceable {

    private Sourceable sourceable;

    public Decorator(Sourceable sourceable) {
        this.sourceable = sourceable;
    }

    @Override
    public void method() {
        sourceable.method();
        System.out.println("赶紧化个妆");
    }

    public static void main(String[] args) {

        Sourceable source = new Source();
        Sourceable decorator = new Decorator(source);
        decorator.method();
    }
}
代理模式

/**
 * @author timevaeless
 * @version 1.0
 * @date 2020/9/2 8:20 PM
 */

/**
 * ①代理模式的作用是什么?
 * - 代理模式就是找一个代理对象替代原来的对象进行一些操作。
 *
 * ②代理模式和装饰者模式有什么区别?
 * - 代理模式关注于替代原有对象去工作
 * - 装饰者模式关注于给原有对象添加新功能
 */

interface ISource {

    void method();
}

class Source2 implements ISource {
    @Override
    public void method() {
        System.out.println("听说我被人代理了?");
    }
}

public class Proxy implements ISource {

    ISource source;

    public Proxy() {
        source = new Source2();
    }

    @Override
    public void method() {
        // source.method();
        System.out.println("我代表Source2来和大家谈判");
    }

    public static void main(String[] args) {

        Proxy proxy = new Proxy();
        proxy.method();
    }
}

Java8 新特性

函数式接口
  • 只包含一个方法的接口
  • 使用@FunctionalInterface注解
Lambda 表达式实现函数式接口
  • () -> {}
方法引用实现函数式接口
  • 是 lambda 表达式的进一步简化
  • 要求函数式接口调用的方法和方法引用的参数和返回值一致
/**
* @author timevaeless
* @version 1.0
* @date 2020/9/4 11:56 AM
*/
public class StreamTest {

   public static void main(String[] args) {

       // ①过滤出集合中的成年人
       ArrayList<Person> people = new ArrayList<>();
       people.add(new Person("刘备", 30));
       people.add(new Person("关羽", 28));
       people.add(new Person("张飞", 17));

       // 在遍历过程中删除会使people长度变短,遍历不到原先长度的位置,导致ConcurrentModificationException
       // for (Person person: people) {
       //     if (!person.isMajor()) {
       //         people.remove(person);
       //     }
       // }

       // 过滤方式一
       final ArrayList<Person> people2 = new ArrayList<>();
       for (Person person: people) {
           if (person.isMajor()) {
               people2.add(person);
           }
       }
       System.out.println(people2);

       // 过滤方式二
       people.stream().filter(person -> person.isMajor()).forEach(person -> {people2.add(person);});
       people.stream().filter(Person::isMajor).forEach(people2::add);

       // ②跳过指定数量
       people.stream().skip(1).forEach(System.out::println);
       // ③只取指定数量
       people.stream().limit(2).forEach(System.out::println);
       // ④将年龄单独提取出来
       people.stream().map(person -> person.age).forEach(System.out::println);
       // ⑤排序
       people.stream().sorted(((o1, o2) -> o1.age - o2.age)).forEach(System.out::println);
       people.stream().sorted(Comparator.comparingInt(o -> o.age)).forEach(System.out::println);
       // people.stream().sorted().forEach(System.out::println);
       // ⑥匹配
       boolean b = people.stream().noneMatch(person -> person.age >= 18); // false - 是否有不匹配条件的元素存在
       b = people.stream().allMatch(person -> person.age >= 18); // false - 是否全都匹配条件
       // ⑦最大最小
       Optional<Person> max = people.stream().max((o1, o2) -> o1.age - o2.age); // Optional[Person{name='刘备', age=30}]
       // ⑧规约
       Optional<Integer> reduce = people.stream().map(person -> person.age).reduce((curr, next) -> curr + next); // Optional[75]
       // ⑨收集
       List<String> collect = people.stream().map(person -> person.name).collect(Collectors.toList()); // [刘备, 关羽, 张飞]
       // ⑩Optional 类 - 可以方便的处理空值,妈妈再也不用担心空指针异常啦!
       String str = null; // 如果我们想看它的长度,则会抛空指针异常了
       Integer length = Optional.ofNullable(str).map(String::length).orElse(0); // 如果是 null,则 length 为 0。
   }
}

Java 9新特性

 /**
 * @author timevaeless
 * @version 1.0
 * @date 2020/9/5 1:44 PM
 */
public class ModuleTest {

    // java.base -> java.lang -> Integer
    // 模块 -> 包 -> 类

    // ①模块化的优势是啥?
    // 按需加载,当当前程序需要使用某个模块下的某一个包的类时,我们无需将该模块整个导入进来,可以只导入该模块下的某一个包即可,从而减少内存开销。
    //


    public static void main(String[] args) throws IOException {

        // Java9 其他新特性
        // 对象不可变,不支持添加删除修改
        // 好处是线程安全,同时防止被源数据被修改
        List<Integer> list = List.of(1, 2, 3, 4, 5);
        Set<Integer> set = Set.of(1, 1, 2, 3, 4);
        Map<String, String> map = Map.of("age", "年龄", "name", "姓名");

        // 可以直接将输入流的数据传输给输出流
        FileInputStream fileInputStream = new FileInputStream("");
        FileOutputStream fileOutputStream = new FileOutputStream("");
        fileInputStream.transferTo(fileOutputStream);
        fileOutputStream.close();
        fileInputStream.close();
      
        // Java10 新特性
        // 本地类型推断 - var
        // 对齐了变量名、更容易阅读
        var num = 10;
        var list = new ArrayList<String>();
        list.add("a");

        for (var v: list) {
            System.out.println(v);
        }

        // Java11 新特性
        // 简化编译 - java HelloWorld.java
        // String 类新增了 isBlank,判断是否为空白,不包括\n
        String content = "      ";
        System.out.println(content.isBlank());
    }
}

项目

需求分析文档

概要设计文档
  • 在线考试系统采用C(Client客户端)/S(Server服务器)架构进行设计,具体如下:
    • 客户端(Client) - 主要用于提供字符界面供用户选择并将处理结果显示出来。
    • 服务器(Server) - 主要用于针对字符界面的选择实现真正业务功能的处理。
    • 数据库(Database) - 主要用于进行数据的存取。

![02 在线考试系统的架构分析](/Users/timevaeless/Documents/学习/Books/模块五Java新特性和项目/03任务三 项目/02_图片/02 在线考试系统的架构分析.png)

详细设计文档
  • 客户端和服务器之间采用基于tcp协议的编程模型进行通信。
    • 客户端的对象输出流连接服务器的对象输入流。
    • 服务器的对象输出流连接客户端的对象输入流。
  • 客户端采用消息的类型作为具体业务的代表,伴随着账户信息等一并发送给服务器。
    • 当客户端发来的消息类型为 "managerCheck" 时,则表示要实现管理员账户信息的校验功能。
    • 当客户端发来的消息类型为"userCheck"时,则表示要实现学员账户信息的校验功能。
  • 服务器采用消息的类型作为是否校验成功的标志发送给客户端。
    • 当客户端收到的消息类型为 "success" 时,则表示账户信息正确。
    • 当客户端收到的消息类型为"fail"时,则表示账户信息错误。
软件的编码流程
  • 管理员登录功能
    • 编写基于tcp协议的服务器端,也就是初始化服务器;
    • 编写基于tcp协议的客户端,来连接服务器;
    • 编写客户端的字符界面并提示客户进行业务的选择;
    • 将客户的选择和输入的相关信息通过对象输出流发送给服务器;
    • 服务器通过对象输入流接收客户端发来的消息并进行功能处理,将处理结果发送给客户端;
    • 客户端通过对象输入流接收服务器的处理结果并给出提示
  • 学员管理系统的功能
    • 当项目启动时,将文件中的所有学员账户信息全部读取出来放到一个List集合中。
    • 客户端输入要增加学员的用户名和密码信息,通过对象输出流发送给服务器。
    • 服务器接收客户端发来的消息,判断集合中是否存在该学员账户信息并实现具体添加功能。
    • 服务器将增加学员功能的处理结果发送给客户端,客户端给出对应的提示。
    • 当项目退出时,将集合中的所有学员账户信息整体写入到文件中。