日拱一卒,功不唐捐。
Algorithm——算法题
杨辉三角
问题描述:
给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。
解题思路:
首先看杨辉三角的规律:
行数跟个数相等
每一行的总和等于2的n-1次方,n为当前行数
每行都是回文
每行第二个数(如果有的话),都是n-1,且n为当前行数
从第一行开始生成的话,奇数行中间那个数,就正好等于上一行中间两个数之和
首先生成当前航的前一半的数字,剩下的根据先前生成的倒序插入即可
处理流程大致上就分为:
计算并标记当前生成行的中位坐标,并标记当前行是否是奇数行(奇数行需要生成回文数列中的最大值)
计算并生成当前行前半部分数据
计算奇数行回文中间数值
将前半部分数值倒序插入当前行中,不全数据
解答代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public List<List<Integer>> generate(int numRows) { List<List<Integer>> resultArray = new ArrayList<>(); List<Integer> subArray = null ; for (int i = 0 ; i < numRows; i++) { subArray = new ArrayList<>(); int mid = (i + 1 ) / 2 ; int mod = (i + 1 ) % 2 ; if (mid < 1 ) { subArray.add(1 ); } for (int j = 0 ; j < mid; j++) { if (j == 0 ) { subArray.add(1 ); continue ; } if (j == 1 ) { subArray.add(i); continue ; } subArray.add(resultArray.get(i - 1 ).get(j) + resultArray.get(i - 1 ).get(j - 1 )); } if (mod == 1 && mid >= 1 ) { subArray.add(resultArray.get(i - 1 ).get(mid) + resultArray.get(i - 1 ).get(mid - 1 )); } for (int k = mid; k > 0 ; k--) { subArray.add(subArray.get(k - 1 )); } resultArray.add(subArray); } return resultArray; } }
Review——阅读一篇英文文章
看法:
HTTP/3 基于 Google 在2012年发布的 QUIC 协议,添加了多项功能,比原来基于TCP/IP协议的HTTP要快非常非常多,QUIC 协议基于 UDP 实现,无需3次握手,具有快速安全的特点
支持情况:
Chrome: 87+
Edge: 87+
Firefox: 88+
Safari: 14+(需要在开启试验功能才能支持)
Tip——学习一个技巧
简单工厂(工厂方法的一种特例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class RuleConfigSource { public RuleConfig load (String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = null ; if ("json" .equalsIgnoreCase(ruleConfigFileExtension)) { parser = new JsonRuleConfigParser(); } else if ("xml" .equalsIgnoreCase(ruleConfigFileExtension)) { parser = new XmlRuleConfigParser(); } else if ("yaml" .equalsIgnoreCase(ruleConfigFileExtension)) { parser = new YamlRuleConfigParser(); } else if ("properties" .equalsIgnoreCase(ruleConfigFileExtension)) { parser = new PropertiesRuleConfigParser(); } else { throw new InvalidRuleConfigException( "Rule config file format is not supported: " + ruleConfigFilePath); } String configText = "" ; RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension (String filePath) { return "json" ; } }
为了让代码逻辑更加清晰,可读性更好,我们要善于将功能独立的代码封装成函数。
我们可以将代码中设计 parser 创建的部分逻辑剥离出来,抽象成 createParser()
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class RuleConfigSource { public RuleConfig load (String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = createParser(ruleConfigFileExtension); if (parser == null ) { throw new InvalidRuleConfigException( "Rule config file format is not supported: " + ruleConfigFilePath); } String configText = "" ; RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension (String filePath) { return "json" ; } private IRuleConfigParser createParser (String configFormat) { IRuleConfigParser parser = null ; if ("json" .equalsIgnoreCase(configFormat)) { parser = new JsonRuleConfigParser(); } else if ("xml" .equalsIgnoreCase(configFormat)) { parser = new XmlRuleConfigParser(); } else if ("yaml" .equalsIgnoreCase(configFormat)) { parser = new YamlRuleConfigParser(); } else if ("properties" .equalsIgnoreCase(configFormat)) { parser = new PropertiesRuleConfigParser(); } return parser; } }
为了让类的职责更加单一、代码更加清晰,我们还可以进一步将 createParser()
函数剥离到一个独立的类中,让这个类只负责对象的创建。
而这个类就是我们所说的 简单工厂模式类 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class RuleConfigSource { public RuleConfig load (String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension); if (parser == null ) { throw new InvalidRuleConfigException( "Rule config file format is not supported: " + ruleConfigFilePath); } String configText = "" ; RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension (String filePath) { return "json" ; } } public class RuleConfigParserFactory { public static IRuleConfigParser createParser (String configFormat) { IRuleConfigParser parser = null ; if ("json" .equalsIgnoreCase(configFormat)) { parser = new JsonRuleConfigParser(); } else if ("xml" .equalsIgnoreCase(configFormat)) { parser = new XmlRuleConfigParser(); } else if ("yaml" .equalsIgnoreCase(configFormat)) { parser = new YamlRuleConfigParser(); } else if ("properties" .equalsIgnoreCase(configFormat)) { parser = new PropertiesRuleConfigParser(); } return parser; } }
在上面的代码实现中,我们每次调用 RuleConfigParserFactory
的 createParser()
的时候,都要创建一个新的 parser。
实际上,如果 parser 可以服用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。
当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用。
有点类似于,单利模式 和 简单工厂模式 的结合。作为简单工厂模式的第二种实现方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class RuleConfigParserFactory { private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>(); static { cachedParsers.put("json" , new JsonRuleConfigParser()); cachedParsers.put("xml" , new XmlRuleConfigParser()); cachedParsers.put("yaml" , new YamlRuleConfigParser()); cachedParsers.put("properties" , new PropertiesRuleConfigParser()); } public static IRuleConfigParser createParser (String configFormat) { if (configFormat == null || configFormat.isEmpty()) { return null ; } IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase()); return parser; } }
在简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下是没有问题的。
应用多态或设计模式来替代 if 分支判断逻辑,它虽然提高了代码的扩展性,更加符合开闭原则,但也增加了类的个数,牺牲了代码的可读性。
工厂方法
如果非要去除掉代码中的 if 分支逻辑,比较经典的方式就是利用多态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public interface IRuleConfigParserFactory { IRuleConfigParser createParser () ; } public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser () { return new JsonRuleConfigParser(); } } public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser () { return new XmlRuleConfigParser(); } } public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser () { return new YamlRuleConfigParser(); } } public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactory { @Override public IRuleConfigParser createParser () { return new PropertiesRuleConfigParser(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class RuleConfigSource { public RuleConfig load (String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParserFactory parserFactory = null ; if ("json" .equalsIgnoreCase(ruleConfigFileExtension)) { parserFactory = new JsonRuleConfigParserFactory(); } else if ("xml" .equalsIgnoreCase(ruleConfigFileExtension)) { parserFactory = new XmlRuleConfigParserFactory(); } else if ("yaml" .equalsIgnoreCase(ruleConfigFileExtension)) { parserFactory = new YamlRuleConfigParserFactory(); } else if ("properties" .equalsIgnoreCase(ruleConfigFileExtension)) { parserFactory = new PropertiesRuleConfigParserFactory(); } else { throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath); } IRuleConfigParser parser = parserFactory.createParser(); String configText = "" ; RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension (String filePath) { return "json" ; } }
解决方法:我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class RuleConfigSource { public RuleConfig load (String ruleConfigFilePath) { String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath); IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension); if (parserFactory == null ) { throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath); } IRuleConfigParser parser = parserFactory.createParser(); String configText = "" ; RuleConfig ruleConfig = parser.parse(configText); return ruleConfig; } private String getFileExtension (String filePath) { return "json" ; } } public class RuleConfigParserFactoryMap { private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>(); static { cachedFactories.put("json" , new JsonRuleConfigParserFactory()); cachedFactories.put("xml" , new XmlRuleConfigParserFactory()); cachedFactories.put("yaml" , new YamlRuleConfigParserFactory()); cachedFactories.put("properties" , new PropertiesRuleConfigParserFactory()); } public static IRuleConfigParserFactory getParserFactory (String type) { if (type == null || type.isEmpty()) { return null ; } IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase()); return parserFactory; } }
问题:什么时候该用工厂方法模式,而非简单工厂模式?
基于单一职责的设计思想,当创建对象逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。
而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得很复杂。
如果对象不可复用,那工厂每次都要返回不同的对象:
使用简单工厂模式实现,包含 if 分支逻辑的实现方式。
避免 if-else 分支逻辑,就推荐使用工厂方法模式。
抽象工厂
场景:
在简单工厂和工厂方法模式中,解析器只根据配置文件格式来分类,但是,如果类有两种分类方式,比如,我们既可以按照配置文件格式来分类,也可以按照解析的对象来分类。
针对这种特殊的场景,如果还是继续用工厂方法来实现的话,我们需要针对每个 parser 都编写一个工厂类。如果未来还需要增加针对业务配置的解析器,那就要再对应地增加工厂类。
过多的类会让系统难维护。
解决方式:
抽象工厂针对这种特殊的场景诞生。我们可以让一个工厂负责创建过个不同类型的对象,而不是只创建一种 parser 对象。
这样可以有效地减少工厂类的个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public interface IConfigParserFactory { IRuleConfigParser createRuleParser () ; ISystemConfigParser createSystemParser () ; } public class JsonConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser () { return new JsonRuleConfigParser(); } @Override public ISystemConfigParser createSystemParser () { return new JsonSystemConfigParser(); } } public class XmlConfigParserFactory implements IConfigParserFactory { @Override public IRuleConfigParser createRuleParser () { return new XmlRuleConfigParser(); } @Override public ISystemConfigParser createSystemParser () { return new XmlSystemConfigParser(); } }
应用工厂模式最本质的参考标准:
封装变化 :创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明;
代码复用 :创建代码抽离到独立的工厂类之后可以复用;
隔离复杂性 :封装复杂的创建逻辑,调用者无需了解如何创建对象;
控制复杂度 :将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁;
为什么没事不要随便用工厂模式创建对象
工厂模式和依赖注入容器
区别
工厂模式中,一个工厂类只负责某个类对象或者某一组相关类对象(继承自同一抽象类或者接口的子类)的创建;
DI 容器负责的是整个应用中所有类对象的创建;
DI 容器还需要负责配置类的解析、对象声明周期的管理;
DI 容器的核心功能
1. 配置解析
背景:
在工厂模式中,工厂类要创建哪个类对象是事先确定好的,并且是写死在工厂类代码中的。作为一个通用的框架来说, 框架代码跟应用代码应该是高度解耦的,DI 容器事先并不知道应用会创建哪些对象 ,不可能把某个应用要创建的对象写死在框架代码中。
所以,我们需要通过一种形式,让应用告知 DI 容器要创建哪些对象。
做法:
将需要由 DI 容器来创建的类对象和创建类对象的必要信息,放到配置文件中。
容器读取配置文件,根据配置文件提供的信息来创建对象。
举例:
Spring 容器的配置文件,定义 beans 的依赖关系以及位置
2. 对象创建
背景:
在 DI 容器中国,如果我们给每个类都创建一个工厂列,那项目中类的个数会成倍增加,这回增加代码的维护成本;
做法:
举例:
Spring 容器中的 BeansFactory。
3. 对象声明周期管理
背景:
简单工厂模式有两种实现方式:一种是每次都返回新创建的对象,另一种是每次都返回同一个事先创建好的对象,也就是所谓的单例对象。
做法:
在 Spring 框架中,我们可以通过配置 scope 属性,来区分这两种不同类型的对象。scope=prototype
表示返回新创建的对象,scope=singleton
表示返回单例对象,默认返回单例;
Spring 框架中,可以配置对象是否支持懒加载。如果 lazy-init=true
,对象在真正被使用到的时候才被创建;如果 lazy-init=false
,对象在应用启动的时候就事先创建好。
配置对象的 init-method
和 destroy-method
方法,如: init-method=loadProperties()
,destroy-method=updateConfigFile()
。
在 DI 容器在创建好对象之后,会主动调用 init-method
属性指定的方法来初始化对象。
在对象被最终销毁之前,DI 容器会主动调用 destroy-method
属性指定的方法来做一些清理工作,比如释放数据库连接池、关闭文件。
实现一个简单的 DI 容器
核心逻辑:
配置文件解析
根据配置文件通过 反射 语法来创建对象
1. 最小原型设计
在最小原型设置中,配置文件中支持的配置语法仅限配置 beans>bean.id.class.scope.lazy-init>constructor-arg.type.value.ref
使用示例:
1 2 3 4 5 6 7 8 9 public class Demo { public static void main (String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "beans.xml" ); RateLimiter rateLimiter = (RateLimiter) applicationContext.getBean("rateLimiter" ); rateLimiter.test(); } }
2. 提供执行入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public interface ApplicationContext { Object getBean (String beanId) ; } public class ClassPathXmlApplicationContext implements ApplicationContext { private BeansFactory beansFactory; private BeanConfigParser beanConfigParser; public ClassPathXmlApplicationContext (String configLocation) { this .beansFactory = new BeansFactory(); this .beanConfigParser = new XmlBeanConfigParser(); loadBeanDefinitions(configLocation); } private void loadBeanDefinitions (String configLocation) { InputStream in = null ; try { in = this .getClass().getResourceAsStream("/" + configLocation); if (in == null ) { throw new RuntimeException("Can not find config file: " + configLocation); } List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in); beansFactory.addBeanDefinitions(beanDefinitions); } finally { if (in != null ) { try { in.close(); } catch (IOException e) { } } } } @Override public Object getBean (String beanId) { return beansFactory.getBean(beanId); } }
ClassPathXmlApplicationContext 负责组装 BeansFactory 和 BeanConfigParser 两个类;
执行流程:
从 classpath 中加载 XML 格式的配置文件,通过 BeanCofigParser 解析为统一的 BeanDefinition 格式;
然后,BeansFactory 根据 BeanDefinition 来创建对象;
3. 配置文件解析
配置文件解析主要包含 BeanConfigParser 接口和 XmlBeanConfigParser 实现类,负责将配置文件解析为 BeanDefinition 结构,以便 BeansFactory 根据这个结构来创建对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public interface BeanConfigParser { List<BeanDefinition> parse (InputStream inputStream) ; List<BeanDefinition> parse (String configContent) ; } public class XmlBeanConfigParser implements BeanConfigParser { @Override public List<BeanDefinition> parse (InputStream inputStream) { String content = null ; return parse(content); } @Override public List<BeanDefinition> parse (String configContent) { List<BeanDefinition> beanDefinitions = new ArrayList<>(); return beanDefinitions; } } public class BeanDefinition { private String id; private String className; private List<ConstructorArg> constructorArgs = new ArrayList<>(); private Scope scope = Scope.SINGLETON; private boolean lazyInit = false ; public boolean isSingleton () { return scope.equals(Scope.SINGLETON); } public static enum Scope { SINGLETON, PROTOTYPE } public static class ConstructorArg { private boolean isRef; private Class type; private Object arg; } }
4. 核心工厂类设计
BeansFactory 根据从配置文件解析得到的 BeanDefinition 来创建对象。
如果对象的 scope 属性是 singleton,那对象创建之后会缓存在 singletonObjects 这样一个 map 中,下次再请求此对象的时候,直接从 map 中取出返回,不需要重新创建。
如果对象的 scope 属性是 prototype,那每次请求对象,BeansFactory 都会创建一个新的对象返回。
BeansFactory 创建对象用到的主要技术点是 Java 中的反射语法。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class BeansFactory { private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>(); public void addBeanDefinitions (List<BeanDefinition> beanDefinitionList) { for (BeanDefinition beanDefinition : beanDefinitionList) { this .beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition); } for (BeanDefinition beanDefinition : beanDefinitionList) { if (beanDefinition.isLazyInit() == false && beanDefinition.isSingleton()) { createBean(beanDefinition); } } } public Object getBean (String beanId) { BeanDefinition beanDefinition = beanDefinitions.get(beanId); if (beanDefinition == null ) { throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanId); } return createBean(beanDefinition); } @VisibleForTesting protected Object createBean (BeanDefinition beanDefinition) { if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition.getId())) { return singletonObjects.get(beanDefinition.getId()); } Object bean = null ; try { Class beanClass = Class.forName(beanDefinition.getClassName()); List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs(); if (args.isEmpty()) { bean = beanClass.newInstance(); } else { Class[] argClasses = new Class[args.size()]; Object[] argObjects = new Object[args.size()]; for (int i = 0 ; i < args.size(); ++i) { BeanDefinition.ConstructorArg arg = args.get(i); if (!arg.getIsRef()) { argClasses[i] = arg.getType(); argObjects[i] = arg.getArg(); } else { BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getArg()); if (refBeanDefinition == null ) { throw new NoSuchBeanDefinitionException("Bean is not defined: " + arg.getArg()); } argClasses[i] = Class.forName(refBeanDefinition.getClassName()); argObjects[i] = createBean(refBeanDefinition); } } bean = beanClass.getConstructor(argClasses).newInstance(argObjects); } } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { throw new BeanCreationFailureException("" , e); } if (bean != null && beanDefinition.isSingleton()) { singletonObjects.putIfAbsent(beanDefinition.getId(), bean); return singletonObjects.get(beanDefinition.getId()); } return bean; } }
思考题
BeansFactory 类中的 createBean() 函数是一个递归函数。当构造函数的参数是 ref 类型时,会递归地创建 ref 属性指向的对象。如果我们在配置文件中错误地配置了对象之间的依赖关系,导致存在循环依赖,那 BeansFactory 的 createBean() 函数是否会出现堆栈溢出?又该如何解决这个问题呢?
demo 位于当前目录下的 dependencycircle
目录下
Share——分享一篇有观点的文章