0%

编程原理

每个程序猿如果理解编程原理和模式的话,那将会很受权益。这个概论是我自己的理解。它可能会对你在设计,讨论或者复习会有所帮助。请注意,它还远远未完成,你需要经常质疑这些原理。

这份列表是受The Principles of Good Programming启发。我认为这份列表很完全,但无法完全与我个人的想法匹配。另外,我需要更详细的资源。如果你有任何建议或者意见,请联系我。

Read more »

Dagger

dagger是square开源的JAVA依赖注入框架,为何我们要使用依赖注入,答案在这里,简而言之依赖注入就是为某个对象提供它所需要的东西(它的依赖),而不是由依赖自己构造,这对于测试很重要。
当然依赖注入不是仅仅只适用于测试,它同样也能很简单的创建可复用,高灵活性的模块。能够在整个应用程序中分享模块,也可以根据开发环境选择运行的模块。

使用Dagger

声明依赖

dagger会构造应用程序类的实例并可以提供它所需的依赖。使用javax.inject.Inject annotation来定义那些构造方法和字段是应用程序需要的。

使用@Inject来注解构造方法来表示Dagger需要构造的类。当需要一个新的实例,Dagger会获取需要的参数并调用相应的构造方法。

1
2
3
4
5
6
7
8
class Thermosiphon implements Pump {
private final Heater heater;

@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
}

Dagger能直接注入字段。这个例子获得一个Header实例和一个Pump实例

1
2
3
4
class CoffeeMaker {
@Inject Heater heater;
@Inject Pump pump;
}

如果你的类有@Inject的字段,但没有@Inject的构造方法,Dagger将会使用无参构造方法,类如果缺少@Inject将无法被Dagger构造.Dagger不支持方法注入。

满足依赖关系

默认情况下,Dagger通过构造实例来满足如上所述的每个依赖.如果你需要一个CoffeeMaker,Dagger将通过new CoffeeMaker()并设置可注入的字段来获取这个实例。

@Inject并不是随处都能有效

  • 接口不能被构造
  • 第三方库的类不能被注解
  • 可配置的对象一定要配置!

对于上述情况@Inject是无法满足的,使用@Provides来注解方法满足一个依赖.方法的返回值类型定义了需要的依赖。
例如,当需要Heater时,provideHeater()会被调用。

1
2
3
@Provides Header provideHeader() {
return new ElectricHeader();
}

对于@Provides来获取他们自己的依赖.当需要Pump时,也需要一个Thermosiphon

所有的@Provides方法必须属于一个模块.这些类有@Module

1
2
3
4
5
6
7
8
9
10
@Module
class DripCoffeeModule {
@Provides Heater provideHeater() {
return new ElectricHeater();
}

@Provides Pump providePump(Thermosiphon pump) {
return pump;
}
}

通常,@Provides方法以provide为前缀,模块的类以Module为后缀

构建视图

含有@Inject@Provides的类是对象的视图,被它们的依赖链接。通过ObjectGraph.create()来获取这个视图,这个视图可以接受一个或者多个modules:

1
ObjectGraph objectGraph = ObjectGraph.create(new DripCoffeeModule());

为了使用视图,我们需要启动注入.这通常需要注入到控制台程序的主入口类里或者是android中的activity。在coffee例子中,CoffeeApp开始依赖注入。我们要求视图提供一个被注入类的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
class CoffeeApp implements Runnable {
@Inject CoffeeMaker coffeeMaker;

@Override public void run() {
coffeeMaker.brew();
}

public static void main(String[] args) {
ObjectGraph objectGraph = ObjectGraph.create(new DripCoffeeModule());
CoffeeApp coffeeApp = objectGraph.get(CoffeeApp.class);
...
}
}

剩下的问题就是CoffeeApp无法被视图识别,我们需要显式的将它注册到@Module模块当中

1
2
3
4
5
6
7
@Module(
injects = CoffeeApp.class
)
class DripCoffeeModule {
...
}

注入的选项在编译期被验证。这能够将问题前移到项目开发早期(重构)
既然视图已经被创建,根对象也已经注入,我们能开始运行coffee maker app了。

@Singletons

@Provides方法或者可注入的类使用@Singleton.视图将会使用这个类对象作为单例。

1
2
3
@Provides @Singleton Heater provideHeater() {
return new ElectricHeater();
}

在一个可注解的类使用@Singleton能被作为文档.它能提醒维护者知道这个类可能被多线程共享

1
2
3
4
@Singleton
class CoffeeMaker {
...
}

懒注入

有时你会需要一个对象延后初始化。你可以延迟创建Lazy<T>直到Lazy<T>get()方法被首次调用。如果T是单例,在ObjectGraph中的Lazy<T>都会是同一个实例。否则都会创建各自的Lazy<T>实例.

1
2
3
4
5
6
7
8
9
10
class GridingCoffeeMaker {
@Inject Lazy<Grinder> lazyGrinder;

public void brew() {
while (needsGrinding()) {
// Grinder created once on first call to .get() and cached.
lazyGrinder.get().grind();
}
}
}

Provider的注入

有时你需要获得多个实例。你有几种选择(工厂,建造者等)一种选择是注入Provider<T>而不是T.当.get()被调用时,一个Provider<T>创建一个T实例

1
2
3
4
5
6
7
8
9
10
11
12
13
class BigCoffeeMaker {
@Inject Provider<Filter> filterProvider;

public void brew(int numberOfPots) {
...
for (int p = 0; p < numberOfPots; p++) {
maker.addFilter(filterProvider.get()); //new filter every time.
maker.addCoffee(...);
maker.percolate();
...
}
}
}

限定符

有时单独的类型无法满足定义一个依赖。例如一个sophisticated coffee maker app想分离加热器的水喝托盘.
在这种情况下,我们添加了一个限定符注解。任何注解都有自己的一个@Qualifier注解

1
2
3
4
5
6
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
String value() default "";
}

你能够创建你自己的限定符注解,或者直接使用@Named.

1
2
3
4
5
class ExpensiveCoffeeMaker {
@Inject @Named("water") Heater waterHeater;
@Inject @Named("hot plate") Heater hotPlateHeater;
...
}
1
2
3
4
5
6
7
@Provides @Named("hot plate") Heater provideHotPlateHeater() {
return new ElectricHeater(70);
}

@Provides @Named("water") Heater provideWaterHeater() {
return new ElectricHeater(93);
}

静态注入(不常用)

1
2
3
4
5
@Module(
staticInjections = LegacyCoffeeUtils.class
)
class LegacyModule {
}

使用ObjectGraph.injectStatics()来赋值这些静态字段

编译期验证

Dagger包含了注解处理器能否验证modulesinjections.这个处理器很严格,如果有任何不合理的绑定都会导致编译器错误.例如这个Module缺少了执行器的绑定:

1
2
3
4
5
6
@Module
class DripCoffeeModule {
@Provides Heater provideHeater(Executor executor) {
return new CpuHeater(executor);
}
}

可以通过为为Executor方法加上@Provides,或者标记这个moduleincomplete.未完成的模块是可以缺少依赖。

1
2
3
4
5
6
@Module(complete = false)
class DripCoffeeModule {
@Provides Heater provideHeater(Executor executor) {
return new CpuHeater(executor);
}
}

模块使用被注入类中没有的类型会触发错误。

1
2
3
4
5
6
7
8
9
@Module(injects = Example.class)
class DripCoffeeModule {
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides Chiller provideChiller() {
return new ElectricChiller();
}
}

上述例子Example里仅仅只是注入了Heater而没有Chiller
如果你的模块的绑定将会被外部使用,可以标记这个模块为library

1
2
3
4
5
6
7
8
9
10
11
12
@Module(
injects = Example.class,
library = true
)
class DripCoffeeModule {
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides Chiller provideChiller() {
return new ElectricChiller();
}
}

为了使编译期验证能够通过,创建一个模块时,可以包含所有项目中的模块。注解处理器将会检测到问题,并报告。

1
2
3
4
5
6
7
8
@Module(
includes = {
DripCoffeeModule.class,
ExecutorModule.class
}
)
public class CoffeeAppModule {
}

编译期代码生成

Dagger的注解处理器可能会生成源文件,这些源文件的名字可能像$InjectAdapter.java或者DripCoffeeModule$ModuleAdapter.这些文件是Dagger的详细实现,你不应该需要直接使用它。虽然它们能够被逐步调试。

模块复写

如果同样的依赖有多个竞争的@Provides方法,Dagger将会编译出错。但有时使用测试代码来替换产品代码是很有必要的。使用@overrides = true在模块注解中,能够用其他模块覆盖当前模块。

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
public class CoffeeMakerTest {
@Inject CoffeeMaker coffeeMaker;
@Inject Heater heater;

@Before public void setUp() {
ObjectGraph.create(new TestModule()).inject(this);
}

@Module(
includes = DripCoffeeModule.class,
injects = CoffeeMakerTest.class,
overrides = true
)
static class TestModule {
@Provides @Singleton Heater provideHeater() {
return Mockito.mock(Heater.class);
}
}

@Test public void testHeaterIsTurnedOnAndThenOff() {
Mockito.when(heater.isHot()).thenReturn(true);
coffeeMaker.brew();
Mockito.verify(heater, Mockito.times(1)).on();
Mockito.verify(heater, Mockito.times(1)).off();
}
}

Dagger2

Dagger2是由Google基于Dagger的基础开发的依赖注入框架.它与Dagger有什么不同呢?主要不同在于Dagger2将Dagger中的Graph替换成了Component

构建视图

在Dagger2中,视图是一个包含多个不带参数的方法的接口,这些方法返回相应的类型.在接口上使用@Componet并传入module类型作为模块的参数,之后Dagger2会完全生成这个接口的实现代码。

1
2
3
4
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}

实现有与接口一样的名字,前缀是Dagger.通过调用builder()获得一个实例

1
2
3
CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
.build();
1
2
3
4
5
6
class Foo {
static class Bar {
@Component
interface BazComponent {}
}
}

上述的@Component将会生成一个名为DaggerFoo_Bar_BazComponent的组件。

Any module with an accessible default constructor can be elided as the builder will construct an instance automatically if none is set. And for any module whose @Provides methods are all static, the implementation doesn’t need an instance at all. If all dependencies can be constructed without the user creating a dependency instance, then the generated implementation will also have a create() method that can be used to get a new instance without having to deal with the builder.

1
CoffeeShop coffeeShop = DaggerCoffeeShop.create();

现在CoffeeApp能简便的使用Dagger生成的CoffeeShop实现代码来拿到一个完全注入的CoffeeMaker

1
2
3
4
5
6
public class CoffeeApp {
public static void main(String[] args) {
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
coffeeShop.maker().brew();
}
}

绑定视图

上述例子展示了如何构建一个带有额外类型绑定的组件,但有各种机制来绑定视图。

  • 那些直接通过@Component.modules或者通过@Module.includes引用的模块当中的方法
  • 任何带有@Inject构造器的类型或者带有@Scope
  • 组件提供了组件依赖的方法
  • 组件自己
  • 任何包含子组件的建造者
  • 以上绑定类型中的ProviderLazy

单例和作用域绑定

1
2
3
@Provides @Singleton static Heater provideHeater() {
return new ElectricHeater();
}
1
2
3
4
@Singleton
class CoffeeMaker {
...
}

Dagger2将组件实现的实例与视图中的作用域实例联系起来了。组件需要声明他们想要对外展示的作用域。

1
2
3
4
5
@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
CoffeeMaker maker();
}

Dagger支持方法注入
Dagger2不支持静态注入
Dagger2不支持Override

….其他的几乎都一样了。

总结

Dagger1中视图是由ObjectGraph 通过反射组成。而dagger2是由@Component,用户定义类型在编译期生成的。从ObjectGraphComponent的转变是Dagger1和Dagger2最大的不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component(
modules = {
ModuleOne.class,
ModuleTwo.class,
ModuleThree.class,
}
)
interface MyComponent {
/* Functionally equivalent to objectGraph.get(Foo.class). */
Foo getFoo();
/* Functionally equivalent to objectGraph.get(Bar.class). */
Bar getBar();
/* Functionally equivalent to objectGraph.inject(aBaz). */
Baz injectBaz(Baz baz);
}

Subset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public List<List<Integer>> subsets(int[] set) {
Arrays.sort(set);
int totalNumber = 1 << set.length;
List<List<Integer>> collection = new ArrayList<List<Integer>>(totalNumber);
for (int i=0; i<totalNumber; i++) {
List<Integer> subSet = new LinkedList<Integer>();
for (int j=0; j<set.length; j++) {
if ((i & (1<<j)) != 0) {
subSet.add(set[j]);
}
}
collection.add(subSet);
}
return collection;
}