设计原则
开发流程
- 需求分析文档 - 分析客户的需求
- 概要设计文档 - 对大概的要点进行设计
- 如采用什么样的架构
- 项目需要分成多少个模块,每个模块之间是什么关系
- 详细设计文档 - 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) - 主要用于进行数据的存取。

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