软件开发流程
1.需求分析文档
2.概要设计文档
3.详细设计文档
4.编码和测试
5.安装与调试
6.升级与维护
常用的设计原则
1.开闭原则(open close principle)
对扩展开放对修改关闭,为了使程序的扩展性好,易于维护和升级。
/**
* 已经提前定义好的类并且已经经通过了测试,根据开闭原则,不建议在类中进行修改
* 但是可以进行扩展,可以考虑创建该类的子类SubPerson,以补充新的需求
*/
public class Person {
//添加成员变量age之后,可能会引起新的问题,
// 后续涉及到Person类的所有代码都需要进行修改,
// 此时之前测试人员所作的测试工作都要重新再来一遍。
//private int age;
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 继承自Person实现扩展,无需更改父类就可以实现扩展的功能。
*/
public class SubPerson extends Person {
private int age;
}
2.里氏代换原则(Liskov Substitution Principle)
任何基类可以出现的地方,子类一定可以出现,多使用多态的方式
public class ShapeTest {
// 自定义成员方法实现将参数指定矩形对象特征打印出来的行为,也就是绘制图形的行为
// Rect r = new Rect(1, 2, 3, 4);
// public static void draw(Rect r) {
// r.show(); // 1 2 3 4
// }
// 自定义成员方法实现将参数指定圆形对象特征打印出来的行为
// public static void draw(Circle c) {
// c.show(); // 5 6 7
// }
// 自定义成员方法实现既能打印矩形对象又能打印圆形对象的特征,对象由参数传入 子类 is a 父类
// Shape s = new Rect(1, 2, 3, 4); 父类类型的引用指向子类类型的对象,形成了多态
// Shape s = new Circle(5, 6, 7); 多态
// 多态的使用场合一:通过参数传递形成了多态
public static void draw(Shape s) {
// 编译阶段调用父类的版本,运行阶段调用子类重写以后的版本
s.show();
}
public static void main(String[] args) {
// Rect r = new Rect(1, 2, 3, 4);
// r.show();
ShapeTest.draw(new Rect(1, 2, 3, 4));
ShapeTest.draw(new Circle(5, 6, 7));
}
}
3.依赖倒转原则(Dependence Inversion Principle)
尽量多依赖于抽象类和接口而不是具体实现类,这样对子类具有强制性和规范性(模板设计)。这里可以参见java多态实现各种形状的打印策略。
4.接口隔离原则(Interface Segregation Principle)
尽量多使用小接口而不是大接口,避免接口的污染,降低类之间耦合度(关联)。
/**
* 大接口,可以描述动物的各种行为
*/
public interface Animal {
void run();//用于描述奔跑行为的抽象方法
void fly();//用于描述飞行行为的抽象方法
}
/**
* 小接口,仅对应动物的一种行为
*/
public interface RunAnimal {
public void run();
}
/**
* 实现Animal接口之后需要重写其所有的方法,
* 但是fly方法明显不是Dog拥有的行为
* 此时用大接口不合适,此时用小接口RunAnimal更合适,
* 避免了牵一发而动全身
*/
//public class Dog implements Animal {
public class Dog implements RunAnimal {
@Override
public void run() {
}
/* @Override
public void fly() {
}*/
}
5.迪米特法则(最少知道原则)(Demeter Principle)
一个实体应当尽量少与其他实体之间发生相互作用,使系统功能模块相对独立。
高内聚,低耦合。
6.合成复用原则(Composite Reuse Principle)
尽量多使用合成/聚合的方式,而不是继承的方式。
public class AA {
public void show(){
System.out.println("这是A类中的show方法");
}
}
/**
* 合成复用原则重点是在避免了 java 单继承的缺陷
*/
public class BB /*extends AA*/ {
//声明一个AA类型的引用作为BB的成员变量,提供构造方法实现初始化,
// 接下来就可以使用这个成员变量调用AA中的成员方法。这就是合成复用原则
private AA a;
public BB(AA a){
this.a = a;
}
public void test(){
//调用A类中的show方法,如何实现?
//第一种方式:继承AA
//第二种方式:使用AA类型的引用
a.show();
}
}
常用的设计模式
基本概念
(1)设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
(2)设计模式就是一种用于固定场合的固定套路。
基本分类
(1)创建型模式:单例设计模式、工厂方法模式、抽象工厂模式。
(2)结构性模式:装饰器模式、代理模式
(3)行为型模式:模板设计模式。
单例设计模式
单例设计模式主要分为:懒汉式和饿汉式。懒汉式需要对多线程同步进行处理。
下面给出了懒汉式线程同步的小案例。
/**
* 单例模式的案例
* 懒汉式线程同步的实现
* 使用synchronized关键字。
*/
public class Singleton {
//2.声明本类类型的引用指向本类类型的对象并使用private static 关键字修饰
private static Singleton sin = null;//懒汉式,容易造成多线程同步问题。
//1.私有化成员构造方法,使用private关键字修饰
private Singleton(){}
//3.提供公有get方法负责将上述对象返回出去,使用public static 关键字修饰
//添加synchronized关键,锁上获得对象的方法,此时无论有多少个线程,
// 仅有一个线程可以调用该方法。
/**
* synchronized修饰方法等价于在方法体内部使用synchronized锁上运行时对象
* @return
*/
public static /*synchronized */Singleton getInstance(){
synchronized(Singleton.class) {//Singleton.class运用了反射机制,获得的是当前正在运行的对象
if (null == sin) {
sin = new Singleton();
}
return sin;
}
}
}
优化懒汉式线程同步问题
最大程度的避免频繁的加锁解锁。
/**
* 单例模式的案例
* 懒汉式线程同步的实现
* 使用synchronized关键字。
*/
public class Singleton {
//2.声明本类类型的引用指向本类类型的对象并使用private static 关键字修饰
private static Singleton sin = null;//懒汉式,容易造成多线程同步问题。
//1.私有化成员构造方法,使用private关键字修饰
private Singleton(){}
//3.提供公有get方法负责将上述对象返回出去,使用public static 关键字修饰
//添加synchronized关键,锁上获得对象的方法,此时无论有多少个线程,
// 仅有一个线程可以调用该方法。
/**
* synchronized修饰方法等价于在方法体内部使用synchronized锁上运行时对象
* @return
*/
public static /*synchronized */Singleton getInstance(){
/* synchronized(Singleton.class) {//Singleton.class运用了反射机制,获得的是当前正在运行的对象
if (null == sin) {
sin = new Singleton();
}
return sin;
}*/
if (null == sin){//判断是否是第一次创建对象,如果不是直接返回对象即可,这样避免了了每个线程调用该方法时都需要上锁,即只需要上锁第一次创建对象。
synchronized(Singleton.class) {//Singleton.class运用了反射机制,获得的是当前正在运行的对象
if (null == sin) {
sin = new Singleton();
}
}
}
return sin;
}
}
普通工厂模式
案例
public class SmsSender implements Sender {
@Override
public void send() {
System.out.println("正在发送短信!");
}
}
public class MailSender implements Sender {
@Override
public void send() {
System.out.println("正在发送邮件....");
}
}
public class SendFactory {
//自定义成员方法实现对象的创建
public Sender produce(String type){
if ("mail".equals(type)){
return new MailSender();
}
if ("sms".equals(type)){
return new SmsSender();
}
return null;
}
}
/**
* 利用普通工厂类中的生产方法produce生产各种类对象。
*/
public class SenderFactoryTest {
public static void main(String[] args) {
//缺点:代码复杂,可读性略差
//优点:扩展性和可维护性更强,尤其是在创建大量对象的前提下
//1.声明工厂类类型的引用指向工厂类类型的对象
SendFactory ss = new SendFactory();
//2.调用生产方法实现对象的创建
Sender s = ss.produce("mail");
//3.使用对象调用方法模拟发送消息
s.send();//控制台输出:正在发送邮件....
System.out.println("====================");
//该方式的优点:代码简单,可读性强, 在创建单个对象时有优势
//缺点:扩展性和可维护性略差
Sender sender = new MailSender();
sender.send();
}
}
缺点:
在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,并且可能出现空指针异常。
多个工厂方法模式
小案例:
public class SendFactory {
//自定义成员方法实现对象的创建
public Sender produce(String type){
if ("mail".equals(type)){
return new MailSender();
}
if ("sms".equals(type)){
return new SmsSender();
}
return null;
}
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
/**
* 利用普通工厂类中的生产方法produce生产各种类对象。
* 利用多个工厂方法模式
*/
public class SenderFactoryTest {
public static void main(String[] args) {
//缺点:代码复杂,可读性略差
//优点:扩展性和可维护性更强,尤其是在创建大量对象的前提下
//1.声明工厂类类型的引用指向工厂类类型的对象
SendFactory ss = new SendFactory();
//2.调用生产方法实现对象的创建
//Sender s = ss.produce("mail");//利用普通工厂模式
Sender s = ss.produceMail();//利用多个工厂方法模式
//缺点:在多个工厂方法模式中,为了能够正确的创建对象,
// 先需要创建工厂类的对象才能调用工厂类中的生产方法
//3.使用对象调用方法模拟发送消息
s.send();//控制台输出:正在发送邮件....
System.out.println("====================");
//该方式的优点:代码简单,可读性强, 在创建单个对象时有优势
//缺点:扩展性和可维护性略差
Sender sender = new MailSender();
sender.send();
}
}
/**
* 利用普通工厂类中的生产方法produce生产各种类对象。
* 利用多个工厂方法模式
*/
public class SenderFactoryTest {
public static void main(String[] args) {
//缺点:代码复杂,可读性略差
//优点:扩展性和可维护性更强,尤其是在创建大量对象的前提下
//1.声明工厂类类型的引用指向工厂类类型的对象
//SendFactory ss = new SendFactory();
//2.调用生产方法实现对象的创建
//Sender s = ss.produce("mail");//利用普通工厂模式
Sender s = SendFactory.produceMail();//利用多个工厂方法模式
//缺点:在多个工厂方法模式中,为了能够正确的创建对象,
// 先需要创建工厂类的对象才能调用工厂类中的生产方法
//3.使用对象调用方法模拟发送消息
s.send();//控制台输出:正在发送邮件....
System.out.println("====================");
//该方式的优点:代码简单,可读性强, 在创建单个对象时有优势
//缺点:扩展性和可维护性略差
Sender sender = new MailSender();
sender.send();
}
}
静态工厂方法模式
public class SendFactory {
//自定义成员方法实现对象的创建
public Sender produce(String type){
if ("mail".equals(type)){
return new MailSender();
}
if ("sms".equals(type)){
return new SmsSender();
}
return null;
}
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
工厂方法模式适合:凡是出现了大量的产品需要创建且具有共同的接口时,可以通过工厂方法模式
进行创建。
上述几种工厂模式的缺点:工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序生产新的产品,就必须对工厂类的代码进行修改,这就违背了开闭原则
抽象工厂模式
小案例:
public interface Provider {
//自定义抽象方法描述产品的生产
Sender produce();
}
public class MailSenderFactory implements Provider {
@Override
public Sender produce() {
return new MailSender();
}
}
public class SmsSenderFactory implements Provider {
@Override
public Sender produce() {
return new SmsSender();
}
}
public class Packet implements Sender{
@Override
public void send() {
System.out.println("正在发送包裹");
}
}
public class PacketSenderFactory implements Provider {
@Override
public Sender produce() {
return new Packet();
}
}
/**
* 利用普通工厂类中的生产方法produce生产各种类对象。
* 利用多个工厂方法模式
* 利用静态工厂模式实现
* 利用抽象工厂模式实现
*
*/
public class SenderFactoryTest {
public static void main(String[] args) {
//缺点:代码复杂,可读性略差
//优点:扩展性和可维护性更强,尤其是在创建大量对象的前提下
//1.声明工厂类类型的引用指向工厂类类型的对象
//SendFactory ss = new SendFactory();
//2.调用生产方法实现对象的创建
//Sender s = ss.produce("mail");//利用普通工厂模式
Sender s = SendFactory.produceMail();//利用多个工厂方法模式
//缺点:在多个工厂方法模式中,为了能够正确的创建对象,
// 先需要创建工厂类的对象才能调用工厂类中的生产方法
//3.使用对象调用方法模拟发送消息
s.send();//控制台输出:正在发送邮件....
System.out.println("====================");
//该方式的优点:代码简单,可读性强, 在创建单个对象时有优势
//缺点:扩展性和可维护性略差
Sender sender = new MailSender();
sender.send();//正在发送邮件....
System.out.println("====================");
//通过抽象工厂模式实现
Provider provider = new MailSenderFactory();
Sender sender2 = provider.produce();
sender2.send();//正在发送包裹
//抽象工厂模式不会违背开闭原则
Provider provider1 = new PacketSenderFactory();
Sender sender3 = provider1.produce();
sender3.send();
}
}
装饰器模式
(1)基本概念:装饰器模式就是给一个对象动态的增加一些新功能,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。
(2)小案例
public interface Sourceable {
void method();
}
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("小迷糊迷迷糊糊!");
}
}
public class Decorator implements Sourceable {
private Sourceable source;
public Decorator(Sourceable source){
this.source = source;
}
@Override
public void method() {
//当传过来的参数是Source时,利用的是父类引用指向子类对象,此时调用source.method()是在Source类中重写的method方法
source.method();//保有原有功能不变
System.out.println("小迷糊装饰之后再也不迷糊");
}
}
/**
* 装饰器模式的实现
*/
public class SourceableTest {
public static void main(String[] args) {
Sourceable sourceable = new Source();
sourceable.method();//小迷糊迷迷糊糊!
System.out.println("==============");
//接下来使用装饰类实现装饰功能
Sourceable sourceable1 = new Decorator(sourceable);
sourceable1.method();
//小迷糊迷迷糊糊!
// 小迷糊装饰之后再也不迷糊
}
}
(3)实际意义:
可以实现一个类功能的扩展
可以动态的增加功能,而且还能动态撤销(继承不行)。
缺点:产生过多相似的对象,不易排错。
代理模式
(1)基本概念:代理模式就是找一个代理类替原对象进行一些操作。比如我们在租房子的时候找中介,再如我们打官司需要请律师,中介和律师在这里就是我们的代理。
(2)小案例:
public class Proxy implements Sourceable {
private Source source;
public Proxy(){
source = new Source();
}
@Override
public void method() {
source.method();
System.out.println("proxy和装饰器其实是不一样的");
}
}
/**
* 装饰器模式的实现
* 代理模式的实现
*/
public class SourceableTest {
public static void main(String[] args) {
//父类引用指向子类对象
Sourceable sourceable = new Source();
sourceable.method();//小迷糊迷迷糊糊!
System.out.println("==============");
//接下来使用装饰类实现装饰功能
//装饰类在利用使用有参数构造方法进行初始化,持有Source类的对象
Sourceable sourceable1 = new Decorator(sourceable);
sourceable1.method();
//小迷糊迷迷糊糊!
// 小迷糊装饰之后再也不迷糊
System.out.println("================");
//代理模式
Sourceable sourceable2 = new Proxy();
sourceable2.method();
//小迷糊迷迷糊糊!
//proxy和装饰器其实是不一样的
}
}
(3)实际意义:如果在使用的时候需要对原有的方法进行改进,可以采用一个代理类调用原有方法,并且对产生的结果进行控制,这种方式就是代理模式。使用代理模式,可以将功能划分的更加清晰,有助于后期维护。
代理模式和装饰器模式的比较
装饰器模式通常的做法是将原始对象作为一个参数传给装饰者的构造器,而代理模式通常在一个代理类中创建一个被代理类的对象。
装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。
模板方法模式
(1)基本概念:
模板方法模式主要指一个抽象类中封装了一个固定流程,流程中的具体步骤可以有不同子类进行不同的实现,通过抽象类让固定的流程产生不同的效果。
(2)小案例:
public abstract class AbstractCalculator {
//自定义成员方法实现将参数指定的表达式按照参数指定的规则进行切割并返回计算结果
public int splitExpression(String exp, String op){
String[] split = exp.split(op);
return calculate(Integer.parseInt(split[0]),Integer.parseInt(split[1]));
}
//自定义抽象方法实现运算
public abstract int calculate(int ia, int ib);
}
public class Plus extends AbstractCalculator {
@Override
public int calculate(int ia, int ib) {
return ia + ib;
}
}
public class Minus extends AbstractCalculator {
@Override
public int calculate(int ia, int ib) {
return ia - ib;
}
}
测试:
public class AbstractCalculatorTest {
public static void main(String[] args) {
AbstractCalculator abstractCalculator = new Plus();
int res = abstractCalculator.splitExpression("1+1","\\+");//+号转义字符,使用时需要注意
System.out.println("最终的运算结果是:" + res);//最终的运算结果是:2
}
}
今日总结:
软件开发流程、常用的设计原则、设计模式的概念和分类、常用的设计模式的详解。必须需要理解透彻。