架构师训练营第 1 期 - 第三周学习总结

用户头像
Anyou Liu
关注
发布于: 2020 年 10 月 03 日

本周学习了设计模式,设计模式是指对一类问题而提出的通用解决方案。设计模式本质上是一些被实践证明的行之有效的解决方案,是前人通过大量工程实践总结出来的,每一种设计模式都是针对某一类的问题。早期我们讲的设计模式一般泛指GOF总结的23中设计模式,现在我们讲的设计模式也包括其他一些大量使用并被反复验证有效的设计方案,比如IoC,MVC等。



GOF的设计模式可以分为创建模式、行为模式和结构模式。创建模式是指创建对象的模式,包括单例、简单工厂、工厂方法等,行为模式是用来设计对象之间行为的模式,包括模板方法、策略模式、观察者等,结构模式是用来描述对象之间如何组合的模式,包括适配器、组合模式、装饰器等。



  1. 单例模式

单例模式适合系统中某个类只需要创建单个实例的场景。比如全局配置类,系统中只需要一个就可以了。SysConfig定义了私有的构造方法,这样SysConfig的实例只能在SysConfig类里面创建,通过静态的getInstance方法返回静态成员变量sysConfig,保证全局只有一个实例。这种在SysConfig类加载的时候就创建单例的方式叫做饿汉方式。

public class SysConfig {
private static SysConfig sysConfig = new SysConfig();
private SysConfig() {
}
public static SysConfig getInstance() {
return sysConfig;
}
}

下面这种在类加载的时候不创建单例,而是在getInstance方法被调用的时候,判断null从而延迟创建的模式又叫做懒汉模式。

public class SysConfig {
private static SysConfig sysConfig;
private SysConfig() {
}
public static SysConfig getInstance() {
if (sysConfig == null) {
sysConfig = new SysConfig();
}
return sysConfig;
}
}

但是懒汉模式在多线程并发的情况下,可能会重复创建。如果2个线程同时执行到sysConfig == null的语句,继而都调用sysConfig = new SysConfig()创建对象,这样就会重复创建。可以通过在getInstance加上synchronized关键字来解决这个问题。

public class SysConfig {
private static SysConfig sysConfig;
private SysConfig() {
}
public static synchronized SysConfig getInstance() {
if (sysConfig == null) {
sysConfig = new SysConfig();
}
return sysConfig;
}
}

但是这样的话,锁的粒度太大了,每次调用getInstance方法,都需要获取类对象的锁。有没有更好的办法呢?可以通过双重检查的方式来解决这个问题,第一次判断sysConfig == null之后,获取类对象的锁,然后再判断空的情况,最后再初始化实例。里面这层的判空必不可少,如果去掉,在多个线程依次获取锁之后,还是会先后重复创建实例,不能保证单例。

public class SysConfig {
private static SysConfig sysConfig;
private SysConfig() {
}
public static SysConfig getInstance() {
if (sysConfig == null) {
synchronized(SysConfig.class) {
if (sysConfig == null) {
sysConfig = new SysConfig();
}
}
}
return sysConfig;
}
}

除了上面这种方式,还有一种更简洁的方式,同时实现了延迟加载和多线程并发的问题。调用getInstance的方法,JVM去加载SysConfigHolder内部类,并初始化类成员sysConfig,因为JVM只会初始化一次,保证了多线程并发的安全访问。

public class SysConfig {
private SysConfig() {
}
public static SysConfig getInstance() {
return SysConfigHolder.sysConfig;
}
private static class SysConfigHolder {
private static SysConfig sysConfig = new SysConfig();
}
}

Spring中也有单例模式,但是Spring的单例实现方式和上面不同。通过注解@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)指定bean在Spring容器里面,只有一个实例,任何注入SysConfig的地方,都是引用的同一个实例。默认情况下,Spring以单例模式创建所有bean。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class SysConfig {
}
  1. 简单工厂模式

简单工厂模式又叫静态工厂模式,是指由一个工厂类来创建具体的哪一类产品。这里的产品类型就是我们定义的产品的子类。比如,我们需要验证客户的真实身份,可以验证护照,也可以验证身份证,我们定义一个验证器Verifier,子类有身份证验证器IdentificationCardVerifier和护照验证器PassportVerifier,通过VerifierFactory的createVerifier方法,根据type的值创建不同的验证器。简单工厂对于客户端来说,静态工厂方法createVerifier屏蔽了对象的创建过程,所有创建的工作都交给了工厂类,客户端不关心对象是如何创建的,只管使用就可以。

public interface Verifier {
boolean verify(String identity);
}



public class IdentificationCardVerifier implements Verifier {
@Override
public boolean verify(String identity) {
System.out.println("验证身份证");
return false;
}
}



public class PassportVerifier implements Verifier {
@Override
public boolean verify(String identity) {
System.out.println("验证护照");
return false;
}
}



public class VerifierFactory {
public static Verifier createVerifier(int type) {
if (type == 1) {
return new IdentificationCardVerifier();
} else if (type == 2) {
return new PassportVerifier();
}
return null;
}
}



public static void main(String[] args) {
Verifier verifier = VerifierFactory.createVerifier(1);
verifier.verify("123");
Verifier verifier2 = VerifierFactory.createVerifier(2);
verifier2.verify("123");
}
}



简单工厂适合系统中产品类型比较明确的场景,如果需求不断变化,要求支持更多的验证方式,比如qq、微信、手机号码等等,并且每一种验证方式支持加密验证和不加密验证,那么增加子类Verifier的同时,会频繁的修改工厂方法createVerifier,并且if else分支会很多,代码变得臃肿,不便于维护。针对这种情况,可以用工厂方法模式。



  1. 工厂方法

工厂方法模式是指工厂方法本身是抽象的,需要具体的工厂子类去实现。比如验证器Verifier有身份证验证器,护照验证器,每一种验证器同时要支持加密验证和不加密验证,我们可以定一个抽象的工厂方法。

public interface Verifier {
boolean verify(String identity);
}



public interface VerifierFactory {
Verifier createVerifier(int type);
}



public class IdentificationCardVerifier implements Verifier {
@Override
public boolean verify(String identity) {
System.out.println("验证身份证");
return false;
}
}



public class IdentificationCardEncryptedVerifier implements Verifier {
@Override
public boolean verify(String identity) {
System.out.println("加密方式验证身份证");
return false;
}
}



public class PassportVerifier implements Verifier {
@Override
public boolean verify(String identity) {
System.out.println("验证护照");
return false;
}
}



public class PassportEncryptedVerifier implements Verifier {
@Override
public boolean verify(String identity) {
System.out.println("加密方式验证护照");
return false;
}
}



public class VerifierFactoryImpl implements VerifierFactory {
@Override
public Verifier createVerifier(int type) {
if (type == 1) {
return new IdentificationCardVerifier();
} else if (type == 2) {
return new PassportVerifier();
}
return null;
}
}



@Override
public Verifier createVerifier(int type) {
if (type == 1) {
return new IdentificationCardEncryptedVerifier();
} else if (type == 2) {
return new PassportEncryptedVerifier();
}
return null;
}
}



public class Main {
public static void main(String[] args) {
VerifierFactory verifierFactory = new VerifierFactoryImpl();
Verifier verifier = verifierFactory.createVerifier(1);
verifier.verify("123");
Verifier verifier2 = verifierFactory.createVerifier(2);
verifier2.verify("123");
VerifierFactory verifierFactory2 = new EncryptedVerifierFactoryImpl();
Verifier verifier21 = verifierFactory2.createVerifier(1);
verifier21.verify("123");
Verifier verifier22 = verifierFactory2.createVerifier(2);
verifier22.verify("123");
}
}

如果系统需要增加一种新的验证方式比如第三方验证,只需要实现新的工厂类和对应的验证器就可以了,不需要修改已有的工厂实现类,是符合开闭原则的。

Spring中也使用了工厂方法模式,BeanFactory是工厂类接口,工厂方法getBean需要具体的工厂子类去实现。XmlWebApplicationContext是从Web容器下面的/WEB-INF/applicationContext.xml读取bean配置的工厂实现类,FileSystemXmlApplicationContext是从文件系统中读取bean配置的工厂实现类,AnnotationConfigApplicationContext是从注解读取bean配置的工厂实现类,还有很多其它工厂实现类,新增加一种读取bean配置的工厂实现类,不会影响已有的代码,实现了扩展。

public interface BeanFactory {
Object getBean(String name) throws BeansException;
}
  1. 模板方法模式

模板方法模式就是把实现逻辑的骨架定义出来,先做什么,后做什么,再做什么,根据流程明确下来,对于其中的某些步骤,定义成抽象方法,强迫子类根据具体情况实现。



比如我们有一个需求,需要从文件读取数据,文件里的每一行映射为一个对象,每一行里的字段由分隔符分隔,每一个文件对应一个实体类,针对这种情况,我们可以用模板方法来实现。实现的大概骨架有了,第一步是从文件读取数据,第二步是把一行的数据根据分隔符转换成字段,第三步是把字段映射为对象。ExtractTemplate里面的extractData方法就定义了抽取数据的步骤,第一步loadData读取数据,第二步splitData转换数据,第三步createDomain创建实体。具体抽取哪个实体的数据,可以继承ExtractTemplate,实现split和create方法。

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public abstract class ExtractTemplate {
public <T> List<T> extractData(String filePath) {
List<String> rawDataList = loadData(filePath);
List<String[]> dataList = splitData(rawDataList);
return createDomain(dataList);
}
protected List<String> loadData(String filePath) {
try {
List<String> dataList = Files.readAllLines(Paths.get(new URI(filePath)));
return dataList;
} catch (IOException e) {
return null;
} catch (URISyntaxException e) {
return null;
}
}
protected abstract String[] split(String rawData);
protected List<String[]> splitData(List<String> rawDataList) {
List<String[]> dataList = new ArrayList<>();
for (String rawData : rawDataList) {
dataList.add(split(rawData));
}
return dataList;
}
protected abstract <T> T create(String[] fields);
protected <T> List<T> createDomain(List<String[]> dataList) {
List<T> domainList = new ArrayList<>();
for (String[] fields : dataList) {
domainList.add(create(fields));
}
return domainList;
}
}

比如我们要抽取Customer实体的数据,CustomerExtractTemplate子类实现了split和create方法。

public class CustomerExtractTemplate extends ExtractTemplate {
@Override
protected String[] split(String rawData) {
return rawData.split("!`");
}
@Override
protected <T> T create(String[] fields) {
Customer customer = new Customer();
customer.setId(fields[0]);
customer.setName(fields[1]);
customer.setAge(fields[2]);
return (T) customer;
}
}



public class Customer {
private String id;
private String name;
private String age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "Customer{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}

比如我们要抽取Address实体的数据,AddressExtractTemplate子类实现了split和create方法。

public class AddressExtractTemplate extends ExtractTemplate {
@Override
protected String[] split(String rawData) {
return rawData.split("!\\^");
}
@Override
protected <T> T create(String[] fields) {
Address address = new Address();
address.setId(fields[0]);
address.setCity(fields[1]);
return (T) address;
}
}



public class Address {
private String id;
private String city;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"id='" + id + '\'' +
", city='" + city + '\'' +
'}';
}
}

调用过程如下,只需指定文件路径,就可以把文件里面的数据映射成实体了。

public class Main {
public static void main(String[] args) {
ExtractTemplate customerExtractTemplate = new CustomerExtractTemplate();
List<Customer> customerList = customerExtractTemplate.extractData("file:/Projects/Study/arch_week3/customer.txt");
System.out.println(customerList);
ExtractTemplate addressExtractTemplate = new AddressExtractTemplate();
List<Address> addressList = addressExtractTemplate.extractData("file:/Projects/Study/arch_week3/address.txt");
System.out.println(addressList);
}
}

输入:

customer.txt
2001!`Jack!`23
2002!`Rose!`30



address.txt
1001!^Shanghai
1002!^Beijing

输出:

[Customer{id='2001', name='Jack', age='23'}, Customer{id='2002', name='Rose', age='30'}]
[Address{id='1001', city='Shanghai'}, Address{id='1002', city='Beijing'}]

Spring Jdbc也使用了模板模式,JdbcTemplate的execute方法里面就定义了查询过程,第一步先获取Connection,第二步创建Statement,第三步设置Statement,第四步执行Statement并映射到实体,第五步关闭Statement和Connection。

public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}

其中第四步又可以再细分步骤出来,先执行Statement,再根据ResultSet映射实体,最后再关闭ResultSet。

public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query [" + sql + "]");
}
/**
* Callback to execute the query.
*/
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}

所以我们在调用query方法的实现,只需要传一个RowMaper就可以了,根据ResultSet映射实体的方法被抽象出来放在RowMapper接口里面了,调用查询获取实体的方法,只需要实现RowMapper的mapRow方法就可以了

@Override
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}



@FunctionalInterface
public interface RowMapper<T> {
/**
* Implementations must implement this method to map each row of data
* in the ResultSet. This method should not call {@code next()} on
* the ResultSet; it is only supposed to map values of the current row.
* @param rs the ResultSet to map (pre-initialized for the current row)
* @param rowNum the number of the current row
* @return the result object for the current row (may be {@code null})
* @throws SQLException if an SQLException is encountered getting
* column values (that is, there's no need to catch SQLException)
*/
@Nullable
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
  1. 策略模式

策略模式就是通过把不同的算法封装到不同的子类里面,这些子类都实现了相同的接口,从而实现了算法可以互相替换。在不影响客户端功能的情况下,实现策略的替换。



比如我们要送快递,把一个包裹从北京运到上海,可以选择空运、铁路运输、公路运输等方式,任何一种运输方式都可以把包裹运到。这里的空运、铁路运输、陆运就是不同的算法。Shipping接口里面的deliveryFromBeijingToShanghai方法就是我们要实现的运输功能,ByAir、ByTrain、ByTruck就是具体的各种运输的策略,通过具体的不同的策略,可以实现算法的替换。用户下订单时,选择不同的运输方式,系统后台就对应不同的算法,同样都实现了运送包裹的功能。

public interface Shipping {
void deliveryFromBeijingToShanghai();
}



public class ByAir implements Shipping {
@Override
public void deliveryFromBeijingToShanghai() {
System.out.println("空运方式");
}
}



public class ByTrain implements Shipping {
@Override
public void deliveryFromBeijingToShanghai() {
System.out.println("铁路方式");
}
}



public class ByTruck implements Shipping {
@Override
public void deliveryFromBeijingToShanghai() {
System.out.println("公路方式");
}
}



public class Main {
public static void main(String[] args) {
Shipping shipping = new ByAir();
shipping.deliveryFromBeijingToShanghai();
shipping = new ByTrain();
shipping.deliveryFromBeijingToShanghai();
shipping = new ByTruck();
shipping.deliveryFromBeijingToShanghai();
}
}



  1. 观察者模式

观察者模式是指当一个对象的状态发生变化时,其它依赖它的对象可以自动接收到变化状态并自动更新。观察者模式里面有观察者和被观察者,被观察者也称作主题,当主题的状态变化时,会通知观察者。



比如我们去餐厅里面点餐,点完餐之后店员给了顾客一个震动器,当食物烹饪完成之后,震动器会响,这样顾客就知道可以去取餐了。这里的食物就是一个主题,当食物的状态是烹饪完成,负责通知震动器,震动器响应状态变化开始震动。

public abstract class Subject {
private List<Observer> observers;
void addObserver(Observer observer) {
if (observers == null) {
observers = new ArrayList<>();
}
observers.add(observer);
}
void notifyObservers(String state) {
if (observers != null) {
for (Observer observer : observers) {
observer.update(state);
}
}
}
}



public interface Observer {
void update(String state);
}



public class Food extends Subject {
private String state;
public void changeState(String state) {
this.state = state;
notifyObservers(state);
}
}



public class Beeper implements Observer {
@Override
public void update(String state) {
if ("烹饪完成".equals(state)) {
System.out.println("烹饪完成,震动器震动");
}
}
}



public class Main {
public static void main(String[] args) {
Food food = new Food();
food.addObserver(new Beeper());
food.changeState("烹饪开始");
food.changeState("烹饪完成");
}
}

观察者模式又叫发布订阅模式,监听器模式。Tomcat中也有应用,比如ServletContextListener就是观察者,可以实现一个或者多个监听器,并通过web.xml配置,把监听器注册到web容器中,当容器启动时,所有监听器的contextInitialized会被执行,当容器停止时,所有监听器的contextDestroyed也会被执行。

public interface ServletContextListener extends EventListener {
void contextInitialized(ServletContextEvent sce);
void contextDestroyed(ServletContextEvent sce);
}
  1. 适配器模式

适配器模式是指将对一个类的接口的调用转换成对另一个类的接口的调用,客户端可以使用另一个类的接口来完成它的工作。



典型的就是转换头,就用到了适配器模式。比如我们去德国出差,要使用国内笔记本的电源插头,但是德国是欧标的插座,是不兼容的,但是有了转换头,就可以帮我们的笔记本适配德国的插座。ChinesePlug是国标插头,ChineseLaptop实现了国标插头,但是没有实现欧标插头EuropeanPlug,我们通过ChineseToEuropeanPlugAdapter适配器,把被适配的对象作为构造参数传入,从而实现了中国的笔记本可以用欧标的插座充电。

public interface ChinesePlug {
void pluginChineseOutlet();
}



public interface EuropeanPlug {
void plugEuropeanOutlet();
}



public class ChineseLaptop implements ChinesePlug {
@Override
public void pluginChineseOutlet() {
System.out.println("国标插头充电");
}
}



public class ChineseToEuropeanPlugAdapter implements EuropeanPlug {
private ChinesePlug chinesePlug;
public ChineseToEuropeanPlugAdapter(ChinesePlug chinesePlug) {
this.chinesePlug = chinesePlug;
}
@Override
public void plugEuropeanOutlet() {
System.out.println("插头转换");
chinesePlug.pluginChineseOutlet();
}
}



public class Main {
public static void main(String[] args) {
EuropeanPlug europeanPlug = new ChineseToEuropeanPlugAdapter(new ChineseLaptop());
europeanPlug.plugEuropeanOutlet();
}
}

JDK中的io流也用到了适配器模式,InputStream是字节输入流,Reader是字符输入流,我们有一个FileInputStream实现了InputStream的read方法,read方法只能用来读取字节,但是如果我们想通过FileInputStream来读取字符,应该怎么做呢?InputStreamReader就是我们的适配器类,它实现了Reader接口,通过把InputStream作为构造参数传入InputStreamReader,就可以使用Reader的read方法读取字符了。



  1. 组合模式

组合模式是用来解决树状结构的一种通用方案,把树状结构中的非叶子节点和它的子节点看作一个整体来处理,处理了这个整体,就是处理了所有的子节点,这样叶子节点和非叶子节点使用就一样了。



比如我们要统计整个学校里面有多少人想要报名参加马拉松,学校有3个年级,1个年级有3个班,如何进行统计?只要我们统计了每个小组先参加的人数,这样从小组汇总到班,班汇总到年级,年级汇总到学校,这样就完成了整个的统计过程。Countable的count方法用来统计人数,addCountable方法用来添加部分到整体。CountableUnit是基类,School、Grade、Classss继承了CountableUnit,School和Grade没有覆盖count方法,Classss覆盖了count方法,实现了具体的统计人数的逻辑。最后把班添加到年级,年级添加到学校,通过调用School的count方法就可以统计人数了。

public interface Countable {
void addCountable(Countable countable);
int count();
}



public abstract class CountableUnit implements Countable {
private String name;
private List<Countable> countableList;
public CountableUnit(String name) {
this.name = name;
}
protected String getName() {
return name;
}
@Override
public void addCountable(Countable countable) {
if (countableList == null) {
countableList = new ArrayList<>();
}
countableList.add(countable);
}
@Override
public int count() {
int total = 0;
if (countableList != null) {
for (Countable countable : countableList) {
total += countable.count();
}
}
return total;
}
}



public class School extends CountableUnit {
public School(String name) {
super(name);
}
}



public class Grade extends CountableUnit {
public Grade(String name) {
super(name);
}
}



public class Classss extends CountableUnit {
private int registerCount;
public Classss(String name, int registerCount) {
super(name);
this.registerCount = registerCount;
}
@Override
public int count() {
System.out.println("汇总 " + getName() + " 马拉松人数为 :" + registerCount);
return registerCount;
}
}



public class Main {
public static void main(String[] args) {
Countable school = new School("学校");
for (int i = 0; i < 3; ++i) {
Countable grade = new Grade((i + 1) + "年级");
school.addCountable(grade);
for (int j = 0; j < 3; ++j) {
Countable classss = new Classss((i + 1) + "年级-" + (j + 1) + "班", j);
grade.addCountable(classss);
}
}
System.out.println("学校参加马拉松的人数为:" + school.count());
}
}

JDK中的Swing,也用到了组合模式,Swing图形界面里面的控件可以组成一个树形结构,控件里面可以包含子控件,java.awt.Component是所有控件的基类,java.awt.Container是可以包含子控件的控件需要继承的类,里面的add方法用来添加一个子控件,调用最顶层控件的paint方法,就可以把所有的控件都画出来了。

  1. 装饰器模式

装饰器模式是指对象原本不具有某种功能,通过装饰的方式赋予对象这种功能。在该模式中,有装饰类和被装饰的类,还有需要实现的功能接口。



比如现实中助听器都实现了装饰器的功能。助听器可以让耳朵听不清的人听到。Hearable定义了hear的功能,HearingAid助听器实现了hear功能,但是耳朵听不清的人PersonWithNotGoodEar本来是听不清的,但是通过把助听器作为构造参数传入,也实现了可以听清的功能。HearingAid就是装饰类,PersonWithNotGoodEar就是被装饰的类。

public interface Hearable {
void hear();
}



public class HearingAid implements Hearable {
@Override
public void hear() {
System.out.println("助听器听到");
}
}



public class PersonWithNotGoodEar implements Hearable {
private Hearable hearable;
public PersonWithNotGoodEar(Hearable hearable) {
this.hearable = hearable;
}
@Override
public void hear() {
this.hearable.hear();
System.out.println("通过助听器耳朵听不清的人也可以听到");
}
}



public class Main {
public static void main(String[] args) {
Hearable hearingAid = new HearingAid();
Hearable hearable = new PersonWithNotGoodEar(hearingAid);
hearable.hear();
}
}
  1. IoC

IoC是Inversion of Control的缩写,就是控制反转。如何来理解呢?加入我们有一个Service的search方法,使用了Dao的doQuery方法进行搜索,Dao是一个接口,DaoImpl实现了这个接口。我们通过在Servie里面直接new一个DaoImpl对象来获取一个Dao的对象。这里Service自己去控制Dao对象的创建。

public class Service {
private Dao dao = new DaoImpl();
public void search() {
dao.doQuery();
}
}



public interface Dao {
void doQuery();
}



public class DaoImpl implements Dao {
@Override
public void doQuery() {
System.out.println("查询");
}
}

这样目前看没什么问题,但是如果之后我们要切换到其它数据源AnotherDaoImpl去做查询,那么Service里面就需要改成用AnotherDaoImpl去创建Dao的实例,这样涉及到修改已有的代码,不符合开闭原则。

public class AnotherDaoImpl implements Dao {
@Override
public void doQuery() {
System.out.println("another查询");
}
}



public class Service {
private Dao dao = new AnotherDaoImpl();
public void search() {
dao.doQuery();
}
}

有没有更好的办法呢?Spring就实现了IoC的方式,跟之前Service自己创建自己需要的实例,在Spring容器里,容器给Service注入它需要的实例,通过bean配置或者Autowired注解的方式就可以了。

public class Service {
@Autowired
private Dao dao;
public void search() {
dao.doQuery();
}
}

如果要切换到AnotherDaoImpl,只需要修改bean配置就可以了,不必修改Service的代码

@Configuration
public class DaoConfig {
@Bean
public Dao getDao() {
return new AnotherDaoImpl();
}
}



  1. MVC

MVC模式是Model、View、Controller的缩写,Model代表实体层,用来生成实体数据,View代表视图层,生成html格式或者其它返回类型,Controller代表控制层,用来请求分发,并跳转到对应的View视图。在传统的jsp里面,把读取数据,显示数据的代码都写在jsp里面,代码揉杂在一起,不够清晰,不易维护。MVC模式把各层分开,url跳转由Controller层负责,数据读取由Model层负责,页面展示由View层负责,代码指责清晰,易于维护。

像Structs1和2、Spring MVC都实现了MVC模式,Spring MVC里面的DispatcherServlet就用来把控制请求分发的:1. 在接收http请求之后,DispatcherServlet根据HandlerMapping里面的配置,把请求转发给对应的Controller 2. Controller根据请求参数获取实体数据,并返回View的名字给DispatcherServlet 3. DispatcherServlet根据ViewResolver决定使用哪个View 4. DispatcherServlet把实体数据发送给View,最终呈现在浏览器里

比如下面的FooController就是控制层的类,barDao.findById()就是获取数据的类,获取到bar数据之后,再返回bar.jsp这个视图层的jsp文件。MVC通过分层的思想,代码更清晰,层次更分明,实现松耦合。

@Controller
@RequestMapping("/foo")
public class FooController {
@Autowired
private BarDao barDao;
@RequestMapping(value = "/bar", method = RequestMethod.GET)
public String bar(@RequestParam(name = "id") Long id, Model model) {
Bar bar = barDao.findById(id);
model.addAttribute("bar", bar);
return "bar";
}
}



public class Bar {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}



public class BarDao {
public Bar findById(Long id) {
Bar bar = new Bar();
bar.setId(id);
bar.setName("Good");
return bar;
}
}



bar.jsp
<html>
<head>
<title>Hello ${bar.name}</title>
</head>

<body>
<h2>Hello ${bar.name}</h2>
</body>
</html>



用户头像

Anyou Liu

关注

还未添加个人签名 2019.05.24 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营第 1 期 - 第三周学习总结