Rust 自定义数据类型

结构体struct

rust不允许只将特定字段标记为可变(很正常,因为结构体应当 作为一个整体考虑)

  • 定义结构体时字段不能添加mut
  • 声明结构体时,语法上也难以做到,字段不是单独声明

结构体中若有字段是引用类型,需要添加生命周期

普通结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct stct{
field1: i32,
field2: String,
}

let field1 = 1;
let stct={
field1,
field2: String::from("fy"),
}
//变量字段同名时字段初始化简略写法

let struct1 = stct{
field1: 1,
..struct2
}
//结构体更新语法

元组结构体

结构体名称提供的含义,但只有字段类型没有字段名,用于命名 元组、指定类型,区别于其他相同(结构)的元组

1
struct tuple_stct=(i32, i32, i32)

类单元结构体unit-like struct

不定义任何字段,类似于()()一般用于泛型中占位,表示 当前类型为空,比如T表示返回值泛型参数,无返回值就可以使用 ()代替,因为Rust中类似于typedef用于自定义类型),常用于 在某个类型上实现trait,但不需要在 类型内存储数据时发挥作用

枚举enum

rust枚举更像C语言中enum+struct

  • enum:定义了新的枚举类型,取值范围有限
  • struct:枚举成员可以关联数据类型,且可以定义方法

枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum IpArr{
V4,
V6,
}
//基础版本

enum IpArr{
V4(u8, u8, u8, u8},
V6(String),
}
//附加数据版本

enum Message{
Quit,
Move{x: i32, y:i32},
Write(String),
ChangeColor(i32, i32, i32),
}
//含有匿名结构体版本

标准库中的枚举

处理null

1
2
3
4
enum Option<T>{
Some<T>,
None,
}

Option被包含在prelude中,包括其成员,rust标准库中唯一 支持创建任何类型枚举值的枚举类型。rust不允许像有效的T类型 数据一样处理Option<T>类型数据,要求在使用之前处理为None 的情况,此即能够保证在可能为空的值会被处理

处理潜在panic

1
2
3
4
enum Result<T, E>{
Ok<T>,
Err<E>,
}

方法、关联函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
impl Message{

fn new() -> Message{
}
//关联函数associated functions,没有`self`作为参数

fn fn1(&self) -> ret_type{
}
//在结构体(枚举、trait对像)的上下文中定义
//第一个参数总是`self`,代表调用方法的结构体实例

fn fn2(mut self) -> ret_type{
}
}

方法 Methods

  • 定义方法的好处主要在于组织性,将某类型实例能做的事均放入 impl

  • 方法签名中self会由rust根据impl关键字后的“替换”为 相应类型(运行过程中是当前实例)

  • 方法可以获取self(当前实例)所有权,常用于将self转换 为其他实例,防止调用者转换之后仍使用原始实例

  • 方法是rust中少数几个可以“自动引用和解引用”的地方,因为 方法中self类型是明确的(调用者类型也明确),rust可以 根据方法签名自动为对象添加&&mut*以适应方法签名, 所以rust调用方法只有.,没有->

关联函数 Associated Functions

与结构体相关联,不作用于一个结构体实例,常被用于返回一个 结构体新实例的构造函数

Trait

将方法(关联函数)签名(可以有默认实现)组合起来、定义实现 某些目的所必需的行为的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pub trait Summarizable{

// 无默认实现
fn author_summary() -> String;

// 有默认实现
fn summary(&self) -> String{
String::from("Read more...{}", self.author_summary())
}
}
//定义trait

impl Summarizable for Message{

fn author_summary(&self){
}

fn summary(&self) -> String{
}

}
//为类型实现trait,之后就可以和普通非trait方法一样调用

默认实现

  • trait中有默认实现的方法可以不重载,实现trait就可直接 调用,没有默认实现的方法则需要全部实现

  • 默认实现重载之后不可能被调用

  • 默认实现可以调用同trait中的其他方法,包括没有默认 实现的方法,如此trait可以实现很多功能而只需要实现少部分

    • 同trait:trait之间本就应该保持独立,这个是trait的 意义

    • 因为实现trait一定要实现所有没有默认实现的方法,所以 默认实现总是“可以调用”

孤儿规则 Orphan Rule

orphan rule:父类型不存在

trait类型位于(之一)本地crate才能实现trait, 如果没有此限制,可能出现两个crate同时对相同类型实现同一trait ,出现冲突

Box<trait> Trait对像

trait对像指向一个实现了指定trait的类型实例,Rust类型系统在 编译时会确保,任何在此上下文中使用的值会实现其trait对像的 trait,如此无需在编译时知晓所有可能类型。

Trait对象、泛型Trait Bound对比

  • trait对像在运行时替代多种具体类型

    • 编译时都是同质的Box<trait>类型
    • 只关心值反映的信息而不是其具体类型,类似于动态语言中 鸭子类型
    • 编译器无法知晓所有可能用于trait对象的类型,因此也 不知道应该调用哪个类型的哪个方法,因此Rust必须使用 动态分发
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    pub trait Draw{
    fn draw(&self);
    }
    pub struct Screen{
    pub components: Vec<Box<Draw>>,
    //`Box<Draw>`就是trait对像,可以代替任何实现了
    //`Draw`trait的值
    }
    impl Screen{
    pub fn run(&self){
    for component in self.components.iter(){
    component.draw();
    }
    }
    }
    pub struct Button{
    pub width: u32,
    pub height: u32,
    pub label: String,
    }
    impl Draw for Button{
    fn Draw{
    }
    }
    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
    // 外部crate使用时
    extern crate rust::gui;
    use rust_gui::{Screen, Button, Draw};

    struct SelectBox{
    width: u32,
    height: u32,
    options: Vec<String>,
    }
    //此类型对于`Screen`是未知的,但是`components`中仍然能够
    //包含此类型
    impl Draw for SelectBox{
    fn draw(&self){
    }
    }
    fn main(){
    let screen = Screen{
    components: vec![
    Box::new(SelectBox{
    width: 75,
    height: 10,
    option: vec![
    String::from("yes"),
    String::from("maybe"),
    ],
    }),
    Box::new(Button{
    width: 50,
    height: 10,
    label: String::from("OK"),
    }),
    ],
    };

    screen.run();
    }
  • trait bound泛型类型参数结构体在编译时单态化

    • 一次只能替代一个具体类型,多个类型之间不同质
    • 单态化产生的代码进行静态分发
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    pub struct Screen<T: Draw>{
    pub components: Vec<T>,
    //trait bound泛型参数`T`只能替代一种类型
    //不同的实现`Draw`trait类型不能放在同一个vector中
    }
    impl<T> Screen<T>
    where T: Draw{
    pub fn run(&self){
    for component in self.components.iter(){
    component.draw();
    }
    }
    }
  • 鸭子类型:如果它走起来像一只鸭子,叫起来像一只鸭子,那么 它就是一直鸭子

  • 静态分发:编译器知晓调用何种方法

  • 动态分发:编译器在编译时不知晓调用何种方法,生成在运行时 确定调用某种方法的代码。动态分发阻止编译器有选择的内联 方法代码,这会禁用部分优化,但获得了额外的灵活性

对象安全

trait对象要求对象安全,只有对象安全的trait才能组成trait 对象,这有一些复杂的规则,但是实践中只涉及

  • 返回值类型不为Self:如果trait中的方法返回Self类型, 而使用trait对象后就不再知晓具体的类型,那方法就不可能 使用已经忘却的原始具体类型(Clonetrait不是对象安全)
  • 方法没有任何泛型类型参数:具体类型实现trait时会放入具体 类型单态化,但是使用trait对象时无法得知具体类型

状态模式(面向对象设计)

  • 值某些内部状态,其行为随着内部状态而改变
  • 内部状态由一系列集成了共享功能的对象表现,每个状态对象 负责自身行为和需要转变为另一个状态时的规则
  • 值对不同状态的行为、何时状态转移不知情,需求改变时无需 改变值持有的状态、值实现代码,只需更新某个状态对象代码 或者是增加更多状态对象
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
pub struct Post{
state: Option<Box<State>>,
content: String,
}
impl Post{

pub fn add_text(&mut self, text: &str){
self.content.push_str(&str);
}

pub fn request_review(&mut self){
if let Some(s) = self.state.take(){
//`Option<T>.take()`返回值,并设置为`None`
self.state = Some(s.request_review())
}
}

pub fn approve(&mut self){
if let Some(s) = self.state.take(){
self.state = Some(s.approve())
}
}

pub fn content(&self) -> &str{
self.state.as_ref().unwrap().content(&self)
//`Option<T>.as_ref()`返回`Option<&T>`,因为参数
//是`&self`,只能获取不可变引用
}
}

trait State{
fn request_review(self: Box<Self>) -> Box<State>;
//`self: Box<Self>`意味着这个方法调用只对`Self`
//类型的`Box`指针有效,这里`Self`表示值类型,因为
//值的类型到struct实现trait的时候才能确定,编译时
//应该会替换成具体类型

//这个方法会获取对象的所有权(消费)

//返回值`Box<State>`是trait对象

fn approve(self: Box<Self>) -> Box<State>;

fn content<'a>(&self, post:&'a Post) -> &'a str{
""
}
}

struct Draft{}

impl State for Draft{
fn request_review(self: Box<Self>) -> Box<State>{
Box::new(PendingReview{})
}

fn approve(self: Box<Self>) -> Box<State>{
self
}
}

struct PendingReview{}

impl State for PendingReview{
fn request_review(self: Box<Self>) -> Box<State>{
self
}

fn approve(self: Box<Self>) -> Box<State>{
Box::new(Published{})
}
}

struct Published{}

impl State for Published{
fn request_review(self: Box<Self>) -> Box<State>{
self
}

fn approve(self: Box<Self>) -> Box<State>{
self
}

fn content<'a>(&self , post:&'a Post) -> &'a str{
&post.content
}
}

高级trait

Associated Type

关联类型:将类型占位符和trait相关联的方式

  • 可在trait方法中使用这些占位符类型
  • 实现trait时需要指定为具体类型
1
2
3
4
5
6
pub trait Iterator{
type Item;
//关联类型`Item`,实现时需要指定具体类型
fn next(&mut self) -> Option<Self::Item>;
//trait方法(签名)中使用关联类型
}

关联类型可以看作时trait中“泛型”(弱化版)。只能实现一次 trait,因此关联类型也只能指定一次,保证了一定的抽象

默认泛型类型参数

使用泛型类型参数时,可为泛型指定默认类型 <PlaceholderType = ConcreteType>

  • 扩展类型而不破坏现有代码(普通trait改为泛型trait不需要 改变之前实现trait的代码)
  • 在特殊情况下自定义trait及其中的方法
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
use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Point{
x: i32,
y: i32,
}
impl Add for Point{
//`Add`是`+`运算符对应的trait
//`Add`有默认类型参数,此时未指定泛型参数的具体类型,
//`RHS`将是默认类型
type Output = Point;

fn add(self, other: Point) -> Point{
Point{
x: self.x + other.x,
y: self.x + other.y,
}
}
}

trait Add<RHS=Self>{
//`Add`trait定义,包含有泛型参数,但是在实现该trait之前
//应该必须要为泛型指定具体类型
//`RHS=Self`就是*默认类型参数*语法,泛型参数`RHS`默认为
//`Self`类型(`+`左值类型)
//RHS:right hand side
type Output;
fn add(self, rhs: RHS) -> Self:Output;
}

运算符重载

Rust不允许创建自定义运算符、重载任意运算符,不过 std::ops中的运算符、相应的trait可以通过实现相关trait重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters{
//`Add`trait中`RHS`不是默认类型`Self`,`Add<Meters>`
//设置`RHS`为`Meters`
//此运算符重载允许`Millmeters`类型和`Meters`类型能够
//直接相加
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters{
Millimters(self.0 + (other.0 * 1000))
}
}

消歧义

Rust无法避免两个trait具有相同名称的方法,也无法阻止某类型 同时实现两个这样的trait(或者是类型已经实现同名方法),此时 需要明确指定使用哪个方法

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
trait Pilot{
fn fly(&self);
}
trait Wizard{
fn fly(&self);
}
struct Human;

impl Pilot for Human{
fn fly(&self){
println!("this is your captain speaking");
}
}
impl Wizard for Human{
fn fly(&self){
println!("up!");
}
}
impl Human{
fn fly(&self){
println!("waving arms furiously!");
}
}

fn main(){
let person = Human;
Pilot::fly(&person);
//`Pilot`trait中方法的消歧义写法
Wizard::fly(&person);
person.fly();
//默认调用直接实现在**类型**上的方法
Person::fly(&person);
//`Person`类型中方法消歧义写法,一般不使用
}

Fully Qualified Syntax

方法获取self参数

  • 不同类型、同方法名,Rust根据self类型可以判断调用何函数
  • 同类型、同方法名,消歧义语法可以指定调用何函数

而对于关联函数,没有self参数,某类型有同名的两个关联函数 时,无法使用消歧义语法指定调用何函数,需要使用完全限定语法 <Type as Trait>::function(receiver_if_method), next_args, ...)

当然,完全限定语法可以用于所有trait方法、关联函数场合,其中 recevier_if_method即表示方法中self参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
trati Animal{
fn baby_name() -> String;
}
struct Dog;
impl Dog{
fn baby_name() -> String{
String::from("Spot")
}
}
impl Animal for Dog{
fn baby_name() -> String{
String::from("puppy")
}
}
fn main(){
println!("A baby dog is called a {}", Dog::baby_name());
//调用`Dog`的关联函数
println!("A baby dog-animal is called a {}", <Dog as Animal>::baby_name());
//完全限定语法
}

Super Trait

有时某个trait可能需要使用另一个trait的功能,要求某类型实现 该trait之前实现被依赖的trait,此所需的trait就是超(父)trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
trait OutlinePrint: fmt::Display{
//`OutlinePrint`trait依赖于`fmt::Display`trait
//在实现`OutlinePrint`之前,需要实现`fmt::Display`
fn outline_print(&self){
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("* {} *", output);
println!("{}", "*".repeat(len+4));
}
}
struct Point{
x: i32,
y: i32,
}
impl fmt::Display for Point{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result{
write!(f, "({}, {})", self,x, self.y)
}
}
impl OutlinePrint for Point{}
//`OutlinePrint`中所有方法均有默认实现

高级类型

Newtype Pattern

孤儿规则限制了只有trait、类型其中只有位于当前crate时,才能对 类型实现trait,使用newtype模式可以“绕过”该限制,即创建新的 元组结构体类型,其中只包含该类型一个成员,此时封装类型对于 crate是本地的。newtype概念源自于Haskell,此模式没有运行时 性能损失,封装类型在编译器时已经省略了

1
2
3
4
5
6
7
8
9
10
11
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper{
fn fmt(&self, f: &mut fmt:Formatter) -> fmt:Result{
write!(f, "[{}]", self.0.join(","))
}
}
fn main(){
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
prinlnt!("w = {}", w);
}

但是Newtype模式中Wrapper是一个新类型,其上没有定义方法, 需要手动实现self.0的所有方法。或者,为Wrapper实现 Dereftrait,并返回self.0,但是此方式下Wrapper会具有 所有self.0的所有方法,如果需要限制封装类型行为,只能自行 实现所需的方法。

Type创建类型别名

type关键字可以给予现有类型别名

  • type不是创建新、单独类型,而是创建别名,而newtype模式 则是真正创建了新类型,也因此无法像newtype模式一样进行 类型检查

    1
    2
    3
    4
    5
    6
    7
    type Kilometers = i32;
    //`type`不是创建新、单独的类型,而是赋予别名,两个类型
    //将得到相同的对待
    let x: i32 = 5;
    let y: Kilometers = 10;
    println!("x + y = {}", x + y);
    //`Kilometers`类型和`i32`类型完全相同,直接进行运算
  • 类型别名主要用途是避免重复

    1
    2
    3
    4
    5
    6
    type Thunk = Box::<Fn() + Send + `static>;
    let f: Thunk = Box::new(|| println!("hi"));
    fn takes_long_type(f: Thunk){
    }
    fn returns_long_type() -> Thunk{
    }

Never Type

Rust中有一个特殊的类型!,被称为empty type(never type)

  • !的正式说法:never type可以强转为其他任何类型
  • 无法被创建
  • 用于发散函数(diverging functions,从不返回的函数)的 返回值

    todo 这个和无返回值函数有何区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let guess: u32 = metch guess.trim().parse(){
Ok(num) => num,
Err(_) => continue,
//`continue`的值即为`!`,`match`分支的返回值必须相同
//而`!`没有值,因此确定`match`的返回值类型为`u32`
};

impl<T> Option<T>{
pub fn unwrap(self) -> T{
// `Option::unwrap`定义
match self{
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"),
// `panic!`是`!`类型,不返回值而是终止程序
}
}
}

println!("forever");
loop{
// 循环永不结束,表达式值是`!`
// 如果加上`break`就不再如此
println!("for ever");
}

Dynamically Sized Types

动态大小类型:“DST”或者“uniszed type”,这些类型允许处理 在运行时才知道大小的类型。Rust需要知道特定类型值需要分配的 内存空间,同类型的值必须使用相同数量的内存,因此必须 将动态大小类型的值至于某种指针之后(此即动态大小类型的 黄金规则),并且使用某些额外的元信息存储动态信息的大小。

str就是动态大小类型,&str则是两个值:str的地址和长度, 这样&str就有了一个在编译时可以知道的大小,并且str可以和 所有类型的指针结合Box<str>Rc<str>。同样的,trait也是 动态大小类型,为了使用trait对象,必须将将其放入指针之后。

Sized trait

Rust自动为编译器在编译时就知道大小的类型实现Sized trait, 且Rust隐式的为每个泛型增加了Sized bound

1
2
3
4
5
6
7
8
9
10
11
12
fn generic<T>(t: T){
}
fn generic<T: Sized>(t: T){
//实际上按照此函数头处理
//即默认情况下,泛型参数不能是DST
}

fn generic<T: ?Sized>(t: &T){
//`?Sized`是特殊的语法,只能用于`Sized` trait不能用于
//其他trait,表示泛型`T`可能不是`Sized`的,此时参数类型
//不能是`T`,必须是指针类型的
}

泛型(generic)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fn largest<T>(list: &[T]) -> T {}
//函数签名中泛型

struct Point<T>{
x: T,
y: T,
}
struct Point<T, U>{
x: T,
y: U,
}
//结构体定义中泛型

enum Option<T>{
Some(T),
None,
}
enum Result<T, E>{
Ok(T),
Err(E),
}
//枚举定义中泛型

方法实现中泛型

  • impl后声明泛型impl<T>表示Point<T>中的T是泛型而 不是具体类型,是对所有的泛型结构体实现

    1
    2
    3
    4
    impl<T> Point<T>{
    fn x(&self) -> &T{
    }
    }
  • impl后不声明泛型,则表示Point<T>T为具体类型, 此时仅对Point<T>类型实现方法

    1
    2
    3
    4
    5
    impl Point<f32>{
    fn x(&self) -> f32{
    }
    }
    //仅`Point<f32>`实现此方法,其他`T`类型没有
  • 结构体定义中的泛型和方法签名中泛型不一定一致

    1
    2
    3
    4
    5
    6
    7
    8
    impl<T, U> Point<T, U>{
    fn mixup<V,W>(self, other:Point<V,W>) -> Point<T,W>{
    Point{
    x: self.x,
    y: other.y,
    }
    }
    }

trait实现中的泛型

1
2
3
4
5
impl<T:Display> ToString for T{
}
// 这个时标准库中的一个例子,使用了trait bounds
// 不使用trait bounds,那感觉有些恐怖。。。

trait定义中的没有泛型,但是其中可以包含泛型方法,同普通 函数

泛型代码的性能

rust在编译时将代码单态化(monomorphization)保证效率,所以 rust使用泛型代码相比具体类型没有任何性能损失

单态化:将泛型代码转变为实际放入的具体类型

1
2
3
4
5
6
7
8
9
10
11
12
13
let integer = Some(5);
let float = Some(5.0);
//单态化
enum Option_i32{
Some(i32),
None,
}
enum Option_f64{
Some(f64),
None,
}
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);

Trait Bounds

指定泛型的trait bounds:限制泛型不再适合任何类型,编译器 确保其被限制为实现特定trait的类型

  • 指定函数泛型trait bounds限制参数类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    pub fn notify<T: Summarizable>(item:T){}
    // 一个trait bound

    pub fn some_fn<T: Display+Clone, U: Debug+Clone>(t:T, u:U) -> 32{}
    // 多个trait bounds

    pub fn some_fn<T, U>(t:T, u:U) -> 32
    where T:Display + Clone,
    U: Debug + Clone{
    }
    // where从句写法
  • 指定方法泛型trait bounds有条件的为某些类型实现

    1
    2
    3
    4
    impl<T: Display+PartialOrd> Point<T>{
    fn cmp_display(&self){
    }
    }

trait和泛型的比较

trait和泛型都是抽象方法

  • trait从方法角度抽象

    • 定义一组公共“行为”
    • “标记(trait bounds)”特定类型(泛型)
  • 泛型从类型的角度抽象

    • 一组(trait bounds)类型定义“项”structenum
    • 一组(trait bounds)类型实现函数、trait
  • trait的定义中不应该出现泛型

    • trait本意应该是定义一组“行为”,需要特定类型实现其方法 (当然有的方法有默认实现),其对应的“对象”不是类型而 是方法,与泛型用途无关
    • trait中定义泛型无意义,trait只是一个“包裹”,真正实现 的是其中的方法,如有必要,定义含有泛型参数的方法即可
    • 若trait中可以使用泛型,则有可能对不同的泛型具体 类型实现“相同”(函数签名没有泛型参数)函数 (在trait中有关联类型提供略弱与泛型的功能)

Hadoop安装配置

Hadoop安装

依赖

  • Java

  • ssh:必须安装且保证sshd一直运行,以便使用hadoop脚本管理 远端hadoop守护进程

    • pdsh:建议安装获得更好的ssh资源管理
    • 要设置免密登陆

机器环境配置

~/.bashrc

这里所有的设置都只是设置环境变量

  • 所以这里所有环境变量都可以放在hadoop-env.sh

  • 放在.bashrc中不是基于用户隔离的考虑

    • 因为hadoop中配置信息大部分放在.xml,放在这里无法 实现用户隔离
    • 更多的考虑是给hive等依赖hadoop的应用提供hadoop配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export HADOOP_PREFIX=/opt/hadoop
# 自定义部分
# 此处是直接解压放在`/opt`目录下
export HADOOP_HOME=$HADOOP_PREFIX
export HADOOP_COMMON_HOME=$HADOOP_PREFIX
# hadoop common
export HADOOP_HDFS_HOME=$HADOOP_PREFIX
# hdfs
export HADOOP_MAPRED_HOME=$HADOOP_PREFIX
# mapreduce
export HADOOP_YARN_HOME=$HADOOP_PREFIX
# YARN
export HADOOP_CONF_DIR=$HADOOP_PREFIX/etc/hadoop

export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native
export HADOOP_OPTS="$HADOOP_OPTS -Djava.library.path=$HADOOP_COMMON_LIB_NATIVE_DIR"
# 这里`-Djava`间不能有空格

export CLASSPATH=$CLASS_PATH:$HADOOP_PREFIX/lib/*
export PATH=$PATH:$HADOOP_PREFIX/sbin:$HADOOP_PREFIX/bin

/etc/hosts

1
2
3
4
192.168.31.129 hd-master
192.168.31.130 hd-slave1
192.168.31.131 hd-slave2
127.0.0.1 localhost
  • 这里配置的ip地址是各个主机的ip,需要自行配置
  • hd-masterhd-slave1等就是主机ip-主机名映射
  • todo?一定需要在/etc/hostname中设置各个主机名称

firewalld

必须关闭所有节点的防火墙

1
2
$ sudo systemctl stop firewalld.service
$ sudo systemctl disable firewalld.service

文件夹建立

  • 所有节点都需要建立
1
2
$ mkdir tmp
$ mkdir -p hdfs/data hdfs/name

Hadoop配置

Hadoop全系列(包括hive、tez等)配置取决于以下两类配置文件

  • 只读默认配置文件

    • core-defualt.xml
    • hdfs-default.xml
    • mapred-default.xml
  • 随站点变化的配置文件

    • etc/hadoop/core-site.xml
    • etc/hadoop/hdfs-site.xml
    • etc/hadoop/mapred-site.xml
    • etc/hadoop/yarn-env.xml
  • 环境设置文件:设置随站点变化的值,从而控制bin/中的 hadoop脚本行为

    • etc/hadoop/hadoop-env.sh
    • etc/hadoop/yarn-env.sh
    • etc/hadoop/mapred-env.sh

    中一般是环境变量配置,补充在shell中未设置的环境变量

  • 注意

    • .xml配置信息可在不同应用的配置文件中继承使用, 如在tez的配置中可以使用core-site.xml${fs.defaultFS}变量

    • 应用会读取/执行相应的*_CONF_DIR目录下所有 .xml/.sh文件,所以理论上可以在etc/hadoop中存放 所以配置文件,因为hadoop是最底层应用,在其他所有应用 启动前把环境均已设置完毕???

Hadoop集群有三种运行模式

  • Standalone Operation
  • Pseudo-Distributed Operation
  • Fully-Distributed Operation

针对不同的运行模式有,hadoop有三种不同的配置方式

Standalone Operation

hadoop被配置为以非分布模式运行的一个独立Java进程,对调试有 帮助

  • 默认为单机模式,无需配置
测试
1
2
3
4
5
$ cd /path/to/hadoop
$ mkdir input
$ cp etc/hadoop/*.xml input
$ bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.1.jar grep input output 'dfs[a-z.]+'
$ cat output/*

Pseudo-Distributed Operation

在单节点(服务器)上以所谓的伪分布式模式运行,此时每个Hadoop 守护进程作为独立的Java进程运行

core-site.xml
1
2
3
4
5
6
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
</configuration>
hdfs-site.xml
1
2
3
4
5
6
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
</configuration>
mapred-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
</configruration>

<configuration>
<property>
<name>mapreduce.application.classpath</name>
<value>$HADOOP_HOME/share/hadoop/mapreduce/*:$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/lib/*</value>
</preperty>
</configruation>
yarn-site.xml
1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.env-whitelist</name>
<value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value>
</property>
</configuration>

Fully-Distributed Operation

  • 单节点配置完hadoop之后,需要将其同步到其余节点
core-site.xml

模板:core-site.xml

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
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://hd-master:9000</value>
<description>namenode address</description>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>file:///opt/hadoop/tmp</value>
</property>
<property>
<name>io.file.buffer.size</name>
<value>131702</value>
</property>

<property>
<name>hadoop.proxyuser.root.hosts</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.root.groups</name>
<value>*</value>
</property>
<!-- 为将用户`root`设置为超级代理,代理所有用户,如果是其他用户需要相应的将root修改为其用户名 -->
<!-- 是为hive的JDBCServer远程访问而设置,应该有其他情况也需要 -->
</configuration>
hdfs-site.xml

模板:hdfs-site.xml

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
<configuration>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>hd-master:9001</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>file:///opt/hadoop/hdfs/name</value>
<description>namenode data directory</description>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>file:///opt/hadoop/hdfs/data</value>
<description>datanode data directory</description>
</property>
<property>
<name>dfs.replication</name>
<value>2</value>
<description>replication number</description>
</property>
<property>
<name>dfs.webhdfs.enabled</name>
<value>true</value>
</property>

<property>
<name>dfs.datanode.directoryscan.throttle.limit.ms.per.sec</name>
<value>1000</value>
</property>
<!--bug-->
</configuration>
yarn-site.xml
  • 模板:yarn-site.xml
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
68
69
70
71
72
73
74
75
76
77
78
79
80
<configuration>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>hd-master</value>
</property>
<property>
<name>yarn.resourcemanager.address</name>
<value>hd-master:9032</value>
</property>
<property>
<name>yarn.resourcemanager.scheduler.address</name>
<value>hd-master:9030</value>
</property>
<property>
<name>yarn.resourcemanager.resource-tracker.address</name>
<value>hd-master:9031</value>
</property>
<property>
<name>yarn.resourcemanager.admin.address</name>
<value>hd-master:9033</value>
</property>
<property>
<name>yarn.resourcemanager.webapp.address</name>
<value>hd-master:9099</value>
</property>

<!-- container -->
<property>
<name>yarn.scheduler.maximum-allocation-mb</name>
<value>512</value>
<description>maximum memory allocation per container</description>
</property>
<property>
<name>yarn.scheduler.minimum-allocation-mb</name>
<value>256</value>
<description>minimum memory allocation per container</description>
</property>
<!-- container -->

<!-- node -->
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>1024</value>
<description>maximium memory allocation per node</description>
</property>
<property>
<name>yarn.nodemanager.vmem-pmem-ratio</name>
<value>8</value>
<description>virtual memmory ratio</description>
</property>
<!-- node -->

<property>
<name>yarn.app.mapreduce.am.resource.mb</name>
<value>384</value>
</property>
<property>
<name>yarn.app.mapreduce.am.command-opts</name>
<value>-Xms128m -Xmx256m</value>
</property>

<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>

<property>
<name>yarn.nodemanager.resource.cpu-vcores</name>
<value>1</value>
</property>

<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name>
<value>org.apache.hadoop.mapred.ShuffleHandler</value>
</property>
</configuration>
mapred-site.xml
  • 模板:mapred-site.xml.template
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
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
<!--
<value>yarn-tez</value>
设置整个hadoop运行在Tez上,需要配置好Tez
-->
</property>
<property>
<name>mapreduce.jobhistory.address</name>
<value>hd-master:10020</value>
</property>
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>hd-master:19888</value>
</property>

<!-- mapreduce -->
<property>
<name>mapreduce.map.memory.mb</name>
<value>256</value>
<description>memory allocation for map task, which should between minimum container and maximum</description>
</property>
<property>
<name>mapreduce.reduce.memory.mb</name>
<value>256</value>
<description>memory allocation for reduce task, which should between minimum container and maximum</description>
</property>
<!-- mapreduce -->

<!-- java heap size options -->
<property>
<name>mapreduce.map.java.opts</name>
<value>-Xms128m -Xmx256m</value>
</property>
<property>
<name>mapreduce.reduce.java.opts</name>
<value>-Xms128m -Xmx256m</value>
</property>
<!-- java heap size options -->

</configuration>
参数说明
  • yarn.scheduler.minimum-allocation-mb:container内存 单位,也是container分配的内存最小值

  • yarn.scheduler.maximum-allocation-mb:container内存 最大值,应该为最小值整数倍

  • mapreduce.map.memeory.mb:map task的内存分配

    • hadoop2x中mapreduce构建于YARN之上,资源由YARN统一管理
    • 所以maptask任务的内存应设置container最小值、最大值间
    • 否则分配一个单位,即最小值container
  • mapreduce.reduce.memeory.mb:reduce task的内存分配

    • 设置一般为map task的两倍
  • *.java.opts:JVM进程参数设置

    • 每个container(其中执行task)中都会运行JVM进程
    • -Xmx...m:heap size最大值设置,所以此参数应该小于 task(map、reduce)对应的container分配内存的最大值, 如果超出会出现physical memory溢出
    • -Xms...m:heap size最小值?#todo
  • yarn.nodemanager.vmem-pmem-ratio:虚拟内存比例

    • 以上所有配置都按照此参数放缩
    • 所以在信息中会有physical memory、virtual memory区分
  • yarn.nodemanager.resource.memory-mb:节点内存设置

    • 整个节点被设置的最大内存,剩余内存共操作系统使用
  • yarn.app.mapreduce.am.resource.mb:每个Application Manager分配的内存大小

主从文件

masters
  • 设置主节点地址,根据需要设置
1
hd-master
slaves
  • 设置从节点地址,根据需要设置
1
2
hd-slave1
hd-slave2

环境设置文件

  • 这里环境设置只是起补充作用,在~/.bashrc已经设置的 环境变量可以不设置
  • 但是在这里设置环境变量,然后把整个目录同步到其他节点, 可以保证在其余节点也能同样的设置环境变量
hadoop-env.sh

设置JAVA_HOME为Java安装根路径

1
JAVA_HOME=/opt/java/jdk
hdfs-env.sh

设置JAVA_HOME为Java安装根路径

1
JAVA_HOME=/opt/java/jdk
yarn-env.sh

设置JAVA_HOME为Java安装根路径

1
2
JAVA_HOME=/opt/java/jdk
JAVA_HEAP_MAX=Xmx3072m

初始化、启动、测试

HDFS
  • 格式化、启动

    1
    2
    3
    4
    5
    6
    $ hdfs namenode -format
    # 格式化文件系统
    $ start-dfs.sh
    # 启动NameNode和DataNode
    # 此时已可访问NameNode,默认http://localhost:9870/
    $ stop-dfs.sh
  • 测试

    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

    $ hdfs dfsadmin -report
    # 应该输出3个节点的情况

    $ hdfs dfs -mkdir /user
    $ hdfs dfs -mkdir /user/<username>
    # 创建执行MapReduce任务所需的HDFS文件夹
    $ hdfs dfs -mkdir input
    $ hdfs dfs -put etc/hadoop/*.xml input
    # 复制文件至分布式文件系统
    $ hadoop jar /opt/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.7.jar grep input output 'dfs[a-z]+'
    # 执行自带样例
    # 样例名称取决于版本

    $ hdfs dfs -get output outut
    $ cat output/*
    # 检查输出文件:将所有的输出文件从分布式文件系统复制
    # 至本地文件系统,并检查
    $ hdfs dfs -cat output/*
    # 或者之间查看分布式文件系统上的输出文件


    $ hadoop jar /opt/hadoop/share/hadoop/tools/lib/hadoop-streaming-2.7.7.jar \
    -input /path/to/hdfs_file \
    -output /path/to/hdfs_dir \
    -mapper "/bin/cat" \
    -reducer "/user/bin/wc" \
    -file /path/to/local_file \
    -numReduceTasks 1
YARN
1
2
3
4
5
$ sbin/start-yarn.sh
# 启动ResourceManger守护进程、NodeManager守护进程
# 即可访问ResourceManager的web接口,默认:http://localhost:8088/
$ sbin/stop-yarn.sh
# 关闭守护进程

其他

注意事项

  • hdfs namenode -format甚至可以在datanode节点没有java时 成功格式化

  • 没有关闭防火墙时,整个集群可以正常启动,甚至可以在hdfs里 正常建立文件夹,但是无法写入文件,尝试写入文件时报错

可能错误

节点启动不全
  • 原因

    • 服务未正常关闭,节点状态不一致
  • 关闭服务、删除存储数据的文件夹dfs/data、格式化namenode

文件无法写入

could only be replicated to 0 nodes instead of minReplication (=1). There are 2 datanode(s) running and 2 node(s) are excluded in this operation.

  • 原因

    • 未关闭防火墙
    • 存储空间不够
    • 节点状态不一致、启动不全
    • 在log里面甚至可能会出现一个连接超时1000ms的ERROR
  • 处理

    • 关闭服务、删除存储数据的文件夹dfs/data、格式化 namenode
      • 这样处理会丢失数据,不能用于生产环境
    • 尝试修改节点状态信息文件VERSION一致
      • ${hadoop.tmp.dir}
      • ${dfs.namenode.name.dir}
      • ${dfs.datanode.data.dir}
Unhealthy Node

1/1 local-dirs are bad: /opt/hadoop/tmp/nm-local-dir; 1/1 log-dirs are bad: /opt/hadoop/logs/userlogs

  • 原因:磁盘占用超过90%

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
scp -r /opt/hadoop/etc/hadoop centos2:/opt/hadoop/etc
scp -r /opt/hadoop/etc/hadoop centos3:/opt/hadoop/etc
# 同步配置

scp /root/.bashrc centos2:/root
scp /root/.bashrc centos3:/root
# 同步环境

rm -r /opt/hadoop/tmp /opt/hadoop/hdfs
mkdir -p /opt/hadoop/tmp /opt/hadoop/hdfs
ssh centos2 rm -r /opt/hadoop/tmp /opt/hadoop/hdfs
ssh centos2 mkdir -p /opt/hadoop/tmp /opt/hadoop/hdfs/name /opt/hadoop/hdfs/data
ssh centos3 rm -r /opt/hadoop/tmp /opt/hadoop/hdfs/name /opt/hadoop/data
ssh centos3 mkdir -p /opt/hadoop/tmp /opt/hadoop/hdfs/name /opt/hadoop/hdfs/data
# 同步清除数据

rm -r /opt/hadoop/logs/*
ssh centos2 rm -r /opt/hadoop/logs/*
ssh centos3 rm -r /opt/hadoop/logs/*
# 同步清除log

Hive

依赖

  • hadoop:配置完成hadoop,则相应java等也配置完成
  • 关系型数据库:mysql、derby等

机器环境配置

~/.bashrc

1
2
3
4
5
export HIVE_HOME=/opt/hive
# self designed
export HIVE_CONF_DIR=$HIVE_HOME/conf
export PATH=$PATH:$HIVE_HOME/bin
export CLASSPATH=$CLASS_PATH:$HIVE_HOME/lib/*

文件夹建立

HDFS
1
2
3
4
$ hdfs dfs -rm -r /user/hive
$ hdfs dfs -mkdir -p /user/hive/warehouse /user/hive/tmp /user/hive/logs
# 这三个目录与配置文件中对应
$ hdfs dfs -chmod 777 /user/hive/warehouse /user/hive/tmp /user/hive/logs
FS
1
2
3
4
5
6
$ mkdir data
$ chmod 777 data
# hive数据存储文件夹
$ mkdir logs
$ chmod 777 logs
# log目录

Hive配置

XML参数

conf/hive-site.xml
  • 模板:conf/hive-default.xml.template
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
68
69
70
71
72
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://hd-master:3306/metastore_db?createDatabaseIfNotExist=true</value>
</property>
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>org.mariadb.jdbc.Driver</value>
</property>
<property>
<name>javax.jdo.option.ConnectionUserName</name>
</value>hive</value>
</property>
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>1234</value>
</property>
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/user/hive/warehouse</value>
</property>
<property>
<name>hive.exec.scratchdir</name>
<value>/user/hive/tmp</value>
</property>

<!--
<property>
<name>hive.exec.local.scratchdir</name>
<value>${system:java.io.tmpdir}/${system:user.name}</value>
</property>
<property>
<name>hive.downloaded.resources.dir</name>
<valeu>${system:java.io.tmpdir}/${hive.session.id}_resources</value>
</property>
<property>«
<name>hive.server2.logging.operation.log.location</name>«
<value>${system:java.io.tmpdir}/${system:user.name}/operation_logs</value>«
<description>Top level directory where operation logs are stored if logging functionality is enabled</description>«
</property>«
所有`${system.java.io.tmpdir}`都要被替换为相应的`/opt/hive/tmp`,
可以通过设置这两个变量即可,基本是用于设置路径
-->

<property>
<name>system:java.io.tmpdir</name>
<value>/opt/hive/tmp</value>
</property>
<property>
<name>system:user.name</name>
<value>hive</value>
<property>

<!--
<property>
<name>hive.querylog.location</name>
<value>/user/hive/logs</value>
<description>Location of Hive run time structured log file</description>
</property>
这里应该不用设置,log放在本地文件系统更合适吧
-->

<property>
<name>hive.metastore.uris</name>
<value>thrift://192.168.31.129:19083</value>
</property>
<!--这个是配置metastore,如果配置此选项,每次启动hive必须先启动metastore,否则hive实可直接启动-->

<property>
<name>hive.server2.logging.operation.enabled</name>
<value>true</value>
</property>
<!-- 使用JDBCServer时需要配置,否则无法自行建立log文件夹,然后报错,手动创建可行,但是每次查询都会删除文件夹,必须查一次建一次 -->
  • /user开头的路径一般表示hdfs中的路径,而${}变量开头 的路径一般表示本地文件系统路径

    • 变量system:java.io.tmpdirsystem:user.name在 文件中需要自己设置,这样就避免需要手动更改出现这些 变量的地方
    • hive.querylog.location设置在本地更好,这个日志好像 只在hive启动时存在,只是查询日志,不是hive运行日志, hive结束运行时会被删除,并不是没有生成日志、${}表示 HDFS路径
  • 配置中出现的目录(HDFS、locaL)有些手动建立

    • HDFS的目录手动建立?
    • local不用
  • hive.metastore.uris若配置,则hive会通过metastore服务 访问元信息

    • 使用hive前需要启动metastore服务
    • 并且端口要和配置文件中一样,否则hive无法访问

环境设置文件

conf/hive-env.sh
  • 模板:conf/hive-env.sh.template
1
2
3
4
5
export JAVA_HOME=/opt/java/jdk
export HADOOP_HOME=/opt/hadoop
export HIVE_CONF_DIR=/opt/hive/conf
# 以上3者若在`~/.bashrc`中设置,则无需再次设置
export HIVE_AUX_JARS_PATH=/opt/hive/lib
conf/hive-exec-log4j2.properties
  • 模板:hive-exec-log4j2.properties.template

    1
    2
    3
    property.hive.log.dir=/opt/hive/logs
    # 原为`${sys:java.io.tmpdir}/${sys:user.name}`
    # 即`/tmp/root`(root用户执行)
conf/hive-log4j2.properties
  • 模板:hive-log4j2.properties.template

MetaStore

MariaDB
  • 安装MariaDB

  • 修改MariaDB配置

    1
    $ cp /user/share/mysql/my-huge.cnf /etc/my.cnf
  • 创建用户,注意新创建用户可能无效,见mysql配置

    • 需要注意用户权限:创建数据库权限、修改表权限
    • 初始化时Hive要自己创建数据库(hive-site中配置), 所以对权限比较严格的环境下,可能需要先行创建同名 数据库、赋权、删库
  • 下载mariadb-java-client-x.x.x-jar包,复制到lib

初始化数据库
1
$ schematool -initSchema -dbType mysql

这个命令要在所有配置完成之后执行

服务设置

1
2
3
4
5
6
7
8
9
10
11
$ hive --service metastore -p 19083 &
# 启动metastore服务,端口要和hive中的配置相同
# 否则hive无法连接metastore服务,无法使用
# 终止metastore服务只能根据进程号`kill`
$ hive --service hiveserver2 --hiveconf hive.server2.thrift.port =10011 &
# 启动JDBC Server
# 此时可以通过JDBC Client(如beeline)连接JDBC Server对
# Hive中数据进行操作
$ hive --service hiveserver2 --stop
# 停止JDBC Server
# 或者直接kill

测试

Hive可用性

需要先启动hdfs、YARN、metastore database(mysql),如果有 设置独立metastore server,还需要在正确端口启动

1
2
3
4
5
6
hive>	create table if not exists words(id INT, word STRING)
row format delimited fields terminated by " "
lines terminated by "\n";
hive> load data local inpath "/opt/hive-test.txt" overwrite into
table words;
hive> select * from words;
JDBCServer可用性
  • 命令行连接

    1
    $ beeline -u jdbc:hive2://localhost:10011 -n hive -p 1234
  • beeline中连接

    1
    2
    3
    $ beeline
    beeline> !connect jdbc:hive2://localhost:10011
    # 然后输入用户名、密码(metastore数据库用户名密码)

其他

可能错误

Failed with exception Unable to move source file

  • linux用户权限问题,无法操作原文件
  • hdfs用户权限问题,无法写入目标文件
  • hdfs配置问题,根本无法向hdfs写入:参见hdfs问题

org.apache.hive.service.cli.HiveSQLException: Couldn’t find log associated with operation handle:

  • 原因:hiveserver2查询日志文件夹不存在

  • 可以在hive中通过

    1
    $ set hive.server2.logging.operation.log.location;

    查询日志文件夹,建立即可,默认为 ${system:java.io.tmpdir}/${system:user.name}/operation_logs ,并设置权限为777

    • 好像如果不设置权限为777,每次查询文件夹被删除,每 查询一次建立一次文件夹?#todo
    • hive-sitex.xml中配置允许自行创建?

User: root is not allowed to impersonate hive

  • 原因:当前用户(不一定是root)不被允许通过代理操作 hadoop用户、用户组、主机

    • hadoop引入安全伪装机制,不允许上层系统直接将实际用户 传递给超级代理,此代理在hadoop上执行操作,避免客户端 随意操作hadoop
  • 配置hadoop的core-site.xml,使得当前用户作为超级代理

Tez

依赖

  • hadoop

机器环境配置

.bashrc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export TEZ_HOME=/opt/tez
export TEZ_CONF_DIR=$TEZ_HOME/conf

for jar in `ls $TEZ_HOME | grep jar`; do
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:$TEZ_HOME/$jar
done
for jar in `ls $TEZ_HOME/lib`; do
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:$TEZ_HOME/lib/$jar
done
# this part could be replaced with line bellow
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:$TEZ_HOME/*:$TEZ_HOME/lib/*
# `hadoop-env.sh`中说`HADOOP_CLASSPATH`是Extra Java CLASSPATH
# elements
# 这意味着hadoop组件只需要把其jar包加到`HADOOP_CLASSPATH`中既可

HDFS

  • 上传$TEZ_HOME/share/tez.tar.gz至HDFS中

    1
    2
    $ hdfs dfs -mkdir /apps
    $ hdfs dfs -copyFromLocal tez.tar.gz /apps

HadoopOnTez

在hadoop中配置Tez

  • 侵入性较强,对已有的hadoop集群全体均有影响

  • 所有hadoop集群执行的MapReduce任务都通过tez执行

    • 这里所有的任务应该是指直接在hadoop上执行、能在 webRM上看到的任务
    • hive这样的独立组件需要独立配置

XML参数

tez-site.xml
  • 模板:conf/tez-default-tmplate.xml
  • 好像还是需要复制到hadoop的配置文件夹中
1
2
3
4
5
6
7
8
9
10
11
<property>
<name>tez.lib.uris</name>
<value>${fs.defaultFS}/apps/tez.tar.gz</value>
<!--设置tez安装包位置-->
</property>
<!--
<property>
<name>tez.container.max.java.heap.fraction</name>
<value>0.2</value>
<property>
内存不足时-->
mapred-site.xml
  • 修改mapred-site.xml文件:配置mapreduce基于yarn-tez, (配置修改在hadoop部分也有)
1
2
3
4
<property>
<name>mapreduce.framework.name</name>
<value>yarn-tez</value>
</property>

环境参数

HiveOnTez

  • 此模式下Hive可以在mapreduce、tez计算模型下自由切换?

    1
    2
    3
    4
    5
    hive> set hive.execution.engine=tez;
    # 切换查询引擎为tez
    hive> set hive.execution.engine=mr;
    # 切换查询引擎为mapreduce
    # 这些命令好像没用,只能更改值,不能更改实际查询模型
  • 只有Hive会受到影响,其他基于hadoop平台的mapreduce作业 仍然使用tez计算模型

Hive设置

  • 若已经修改了mapred-site.xml设置全局基于tez,则无需复制 jar包,直接修改hive-site.xml即可
Jar包复制

复制$TEZ_HOME$TEZ_HOME/lib下的jar包到$HIVE_HOME/lib 下即可

hive-site.xml
1
2
3
4
<property>
<name>hive.execution.engine</name>
<value>tez</value>
</property>

其他

可能错误

SLF4J: Class path contains multiple SLF4J bindings.

  • 原因:包冲突的
  • 解决方案:根据提示冲突包删除即可

Spark

依赖

  • java
  • scala
  • python:一般安装anaconda,需要额外配置
    1
    2
    export PYTHON_HOME=/opt/anaconda3
    export PATH=$PYTHON_HOME/bin:$PATH
  • 相应资源管理框架,如果不以standalone模式运行

机器环境配置

~/.bashrc

1
2
3
4
5
6
7
export SPARK_HOME=/opt/spark
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin
export PYTHON_PATH=$PYTHON_PATH:$SPARK_HOME/python:$SPARK_HOME/python/lib/*
# 把`pyshark`、`py4j`模块对应的zip文件添加进路径
# 这里用的是`*`通配符应该也可以,手动添加所有zip肯定可以
# 否则无法在一般的python中对spark进行操作
# 似乎只要master节点有设置`/lib/*`添加`pyspark`、`py4j`就行

Standalone

环境设置文件

conf/spark-env.sh
  • 模板:conf/spark-env.sh.template

这里应该有些配置可以省略、移除#todo

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
export JAVA_HOME=/opt/jdk
export HADOOP_HOME=/opt/hadoop
export hADOOP_CONF_DIR=/opt/hadoop/etc/hadoop
export HIVE_HOME=/opt/hive

export SCALA_HOME=/opt/scala
export SCALA_LIBRARY=$SPARK_HOME/lib
# `~/.bashrc`设置完成之后,前面这段应该就这个需要设置

export SPARK_HOME=/opt/spark
export SPARK_DIST_CLASSPATH=$(hadoop classpath)
# 这里是执行命令获取classpath
# todo
# 这里看文档的意思,应该也是类似于`$HADOOP_CLASSPATH`
# 可以直接添加进`$CLASSPATH`而不必设置此变量
export SPARK_LIBRARY_PATH=$SPARK_HOME/lib

export SPARK_MASTER_HOST=hd-master
export SPARK_MASTER_PORT=7077
export SPARK_MASTER_WEBUI_PORT=8080
export SPARK_WORKER_WEBUI_PORT=8081
export SPARK_WORKER_MEMORY=1024m
# spark能在一个container内执行多个task
export SPARK_LOCAL_DIRS=$SPARK_HOME/data
# 需要手动创建

export SPARK_MASTER_OPTS=
export SPARK_WORKER_OPTS=
export SPARK_DAEMON_JAVA_OPTS=
export SPARK_DAEMON_MEMORY=
export SPARK_DAEMON_JAVA_OPTS=
文件夹建立
1
2
$ mkdir /opt/spark/spark_data
# for `$SPARK_LOCAL_DIRS`

Spark配置

conf/slaves

文件不存在,则在当前主机单节点运行

  • 模板:conf/slaves.template
1
2
hd-slave1
hd-slave2
conf/hive-site.xml

这里只是配置Spark,让Spark作为“thrift客户端”能正确连上 metastore server

  • 模板:/opt/hive/conf/hive-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hive.metastore.uris</name>
<value>thrift://192.168.31.129:19083</value>
<description>Thrift URI for the remote metastor. Used by metastore client to connect to remote metastore</description>
</property>
<property>
<name>hive.server2.thrift.port</name>
<value>10011</value>
</property>
<!--配置spark对外界thrift服务,以便可通过JDBC客户端存取spark-->
<!--这里启动端口同hive的配置,所以两者不能默认同时启动-->
<property>
<name>hive.server2.thrift.bind.host</name>
<value>hd-master</value>
</property>
</configuration>

测试

启动Spark服务

需要启动hdfs、正确端口启动的metastore server

1
2
3
4
5
6
7
8
9
10
$ start-master.sh
# 在执行**此命令**机器上启动master实例
$ start-slaves.sh
# 在`conf/slaves`中的机器上启动worker实例
$ start-slave.sh
# 在执行**此命令**机器上启动worker实例

$ stop-master.sh
$ stop-slaves.sh
$ stop-slave.sh
启动Spark Thrift Server
1
2
3
4
5
6
7
$ start-thriftserver.sh --master spark://hd-master:7077 \
--hiveconf hive.server2.thrift.bind.host hd-master \
--hiveconf hive.server2.thrift.port 10011
# 这里在命令行启动thrift server时动态指定host、port
# 如果在`conf/hive-site.xml`有配置,应该不需要

# 然后使用beeline连接thrift server,同hive
Spark-Sql测试
1
2
3
4
5
6
$ spark-sql --master spark://hd-master:7077
# 在含有配置文件的节点上启动时,配置文件中已经指定`MASTER`
# 因此不需要指定后面配置

spark-sql> set spark.sql.shuffle.partitions=20;
spark-sql> select id, count(*) from words group by id order by id;
pyspark测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ MASTER=spark://hd-master:7077 pyspark
# 这里应该是调用`$PATH`中第一个python,如果未默认指定

from pyspark.sql import HiveContext
sql_ctxt = HiveContext(sc)
# 此`sc`是pyspark启动时自带的,是`SparkContext`类型实例
# 每个连接只能有一个此实例,不能再次创建此实例

ret = sql_ctxt.sql("show tables").collect()
# 这里语句结尾不能加`;`

file = sc.textFile("hdfs://hd-master:9000/user/root/input/capacity-scheduler.xml")
file.count()
file.first()
Scala测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ MASTER=spark://hd-master:7077 spark-shell \
executor-memory 1024m \
--total-executor-cores 2 \
--excutor-cores 1 \
# 添加参数启动`spark-shell`

import org.apache.spark.sql.SQLContext
val sqlContext = new org.apache.spark.sql.hive.HiveContext(sc)
sqlContext.sql("select * from words").collect().foreach(println)
sqlContext.sql("select id, word from words order by id").collect().foreach(println)

sqlContext.sql("insert into words values(7, \"jd\")")
val df = sqlContext.sql("select * from words");
df.show()

var df = spark.read.json("file:///opt/spark/example/src/main/resources/people.json")
df.show()

Spark on YARN

其他

可能错误

Initial job has not accepted any resources;

  • 原因:内存不足,spark提交application时内存超过分配给 worker节点内存

  • 说明

    • 根据结果来看,pysparkspark-sql需要内存比 spark-shell少? (设置worker内存512m,前两者可以正常运行)
    • 但是前两者的内存分配和scala不同,scala应该是提交任务 、指定内存大小的方式,这也可以从web-ui中看出来,只有 spark-shell开启时才算是application
  • 解决方式

    • 修改conf/spark-env.shSPARK_WORKER_MEMORY更大, (spark默认提交application内存为1024m)
    • 添加启动参数--executor-memory XXXm不超过分配值
ERROR KeyProviderCache:87 - Could not find uri with key [dfs.encryption.key.provider.uri] to create a keyProvider
  • 无影响

HBase

依赖

  • java
  • hadoop
  • zookeeper:建议,否则日志不好管理

机器环境

~/.bashrc

1
2
3
export HBASE_HOME=/opt/hbase
export PATH=$PAHT:$HBASE_HOME/bin
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:$HBASE_HOME/lib/*

建立目录

1
$ mkdir /tmp/hbase/tmpdir

HBase配置

环境变量

conf/hbase-env.sh
1
2
export HBASE_MANAGES_ZK=false
# 不使用自带zookeeper
conf/zoo.cfg

若设置使用独立zookeeper,需要复制zookeeper配置至HBase配置 文件夹中

1
$ cp /opt/zookeeper/conf/zoo.cfg /opt/hbase/conf

Standalone模式

conf/hbase-site.xml
1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>hbase.rootdir</name>
<value>file://${HBASE_HOME}/data</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/tmp/zookeeper/zkdata</value>
</property>
</configuration>

Pseudo-Distributed模式

conf/hbase-site.xml
  • 在Standalone配置上修改
1
2
3
4
5
6
7
8
<proeperty>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.rootdir</name>
<value>hdfs://hd-master:9000/hbase</value>
</property>

Fully-Distributed模式

conf/hbase-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<property>
<name>hbase.rootdir</name>
<value>hdfs://hd-master:9000/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</name>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>hd-master,hd-slave1,hd-slave2</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/tmp/zookeeper/zkdata</value>
</property>

测试

  • 需要首先启动HDFS、YARN
  • 使用独立zookeeper还需要先行在每个节点启动zookeeper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ start-hbase.sh
# 启动HBase服务
$ local-regionservers.sh start 2 3 4 5
# 启动额外的4个RegionServer
$ hbase shell
hbase> create 'test', 'cf'
hbase> list 'test'
hbase> put 'test', 'row7', 'cf:a', 'value7a'
put 'test', 'row7', 'cf:b', 'value7b'
put 'test', 'row7', 'cf:c', 'value7c'
put 'test', 'row8', 'cf:b', 'value8b',
put 'test', 'row9', 'cf:c', 'value9c'
hbase> scan 'test'
hbase> get 'test', 'row7'
hbase> disable 'test'
hbase> enable 'test'
hbaee> drop 'test'
hbase> quit

Zookeeper

依赖

  • java

  • 注意:zookeeper集群中工作超过半数才能对外提供服务,所以 一般配置服务器数量为奇数

机器环境

~/.bashrc

1
2
3
export ZOOKEEPER_HOME=/opt/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:$ZOOKEEPER_HOME/lib

创建文件夹

  • 在所有节点都需要创建相应文件夹、myid文件
1
2
3
4
5
6
7
mkdir -p /tmp/zookeeper/zkdata /tmp/zookeeper/zkdatalog
echo 0 > /tmp/zookeeper/zkdatalog/myid

ssh centos2 mkdir -p /tmp/zookeeper/zkdata /tmp/zookeeper/zkdatalog
ssh centos3 mkdir -p /tmp/zookeeper/zkdata /tmp/zookeeper/zkdatalog
ssh centos2 "echo 2 > /tmp/zookeeper/zkdata/myid"
ssh centos3 "echo 3 > /tmp/zookeeper/zkdata/myid"

Zookeeper配置

Conf

conf/zoo.cfg
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
tickTime=2000
# The number of milliseconds of each tick
initLimit=10
# The number of ticks that the initial
# synchronization phase can take
syncLimit=5
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
dataDir=/tmp/zookeeper/zkdata
dataLogDir=/tmp/zookeeper/zkdatalog
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
clientPort=2181
# the port at which the clients will connect

autopurge.snapRetainCount=3
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
# The number of snapshots to retain in dataDir
autopurge.purgeInterval=1
# Purge task interval in hours
# Set to "0" to disable auto purge feature

server.0=hd-master:2888:3888
server.1=hd-slave1:2888:3888
server.2=hd-slave2:2888:3888
# Determine the zookeeper servers
# fromation: server.NO=HOST:PORT1:PORT2
# PORT1: port used to communicate with leader
# PORT2: port used to reelect leader when current leader fail
$dataDir/myid
  • $dataDirconf/zoo.cfg中指定目录
  • myid文件里就一个id,指明当前zookeeper server的id,服务 启动时读取文件确定其id,需要自行创建
1
0

启动、测试、清理

启动zookeeper

1
2
3
4
5
6
7
8
9
$ zkServer.sh start
# 开启zookeeper服务
# zookeeper服务要在各个节点分别手动启动

$ zkServer.sh status
# 查看服务状态

$ zkCleanup.sh
# 清理旧的快照、日志文件

Flume

依赖

  • java

机器环境配置

~/.bashrc

1
export PATH=$PATH:/opt/flume/bin

Flume配置

环境设置文件

conf/flume-env.sh
  • 模板:conf/flume-env.sh.template
1
JAVA_HOME=/opt/jdk

Conf文件

conf/flume.conf
  • 模板:conf/flume-conf.properties.template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
agent1.channels.ch1.type=memory
# define a memory channel called `ch1` on `agent1`
agent1.sources.avro-source1.channels=ch1
agent1.sources.avro-source1.type=avro
agent1.sources.avro-source1.bind=0.0.0.0
agent1.sources.avro-source1.prot=41414
# define an Avro source called `avro-source1` on `agent1` and tell it
agent1.sink.log-sink1.channels=ch1
agent1.sink.log-sink1.type=logger
# define a logger sink that simply logs all events it receives
agent1.channels=ch1
agent1.sources=avro-source1
agent1.sinks=log-sink1
# Finally, all components have been defined, tell `agent1` which one to activate

启动、测试

1
2
3
4
5
6
7
8
9
10
11
$ flume-ng agent --conf /opt/flume/conf \
-f /conf/flume.conf \
-D flume.root.logger=DEBUG,console \
-n agent1
# the agent name specified by -n agent1` must match an agent name in `-f /conf/flume.conf`

$ flume-ng avro-client --conf /opt/flume/conf \
-H localhost -p 41414 \
-F /opt/hive-test.txt \
-D flume.root.logger=DEBUG, Console
# 测试flume

其他

Kafka

依赖

  • java
  • zookeeper

机器环境变量

~/.bashrc

1
2
export PATH=$PATH:/opt/kafka/bin
export KAFKA_HOME=/opt/kafka

多brokers配置

Conf

config/server-1.properties
  • 模板:config/server.properties
  • 不同节点broker.id不能相同
  • 可以多编写几个配置文件,在不同节点使用不同配置文件启动
1
2
3
broker.id=0
listeners=PLAINTEXT://:9093
zookeeper.connect=hd-master:2181, hd-slave1:2181, hd-slave2:2181

测试

  • 启动zookeeper
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
$ kafka-server-start.sh /opt/kafka/config/server.properties &
# 开启kafka服务(broker)
# 这里是指定使用单个默认配置文件启动broker
# 启动多个broker需要分别使用多个配置启动多次
$ kafka-server-stop.sh /opt/kafka/config/server.properties

$ kafka-topics.sh --create --zookeeper localhost:2181 \
--replication-factor 1 \
--partitions 1 \
--topic test1
# 开启话题
$ kafka-topics.sh --list zookeeper localhost:2181
#
$ kafka-topics.shd --delete --zookeeper localhost:2181
--topic test1
# 关闭话题

$ kafka-console-producer.sh --broker-list localhost:9092 \
--topic test1
# 新终端开启producer,可以开始发送消息

$ kafka-console-consumer.sh --bootstrap-server localhost:9092 \
--topic test1 \
--from-beginning

$ kafka-console-consumer.sh --zookeeper localhost:2181 \
--topic test1 \
--from beginning
# 新终端开启consumer,可以开始接收信息
# 这个好像是错的

其他

Storm

依赖

  • java
  • zookeeper
  • python2.6+
  • ZeroMQ、JZMQ

机器环境配置

~/.bashrc

1
2
export STORM_HOME=/opt/storm
export PAT=$PATH:$STORM_HOME/bin

Storm配置

配置文件

conf/storm.yaml
  • 模板:conf/storm.yarml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
storm.zookeeper.servers:
-hd-master
-hd-slave1
-hd-slave2
storm.zookeeper.port: 2181

nimbus.seeds: [hd-master]
storm.local.dir: /tmp/storm/tmp
nimbus.host: hd-master
supervisor.slots.ports:
-6700
-6701
-6702
-6703

启动、测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
storm nimbus &> /dev/null &
storm logviewer &> /dev/null &
storm ui &> /dev/null &
# master节点启动nimbus

storm sueprvisor &> /dev/null &
storm logviewer &> /dev/nulla &
# worker节点启动


storm jar /opt/storm/example/..../storm-start.jar \
storm.starter.WordCountTopology
# 测试用例
stom kill WordCountTopology

http://hadoop.apache.org/docs/r3.1.1

设计模式简介

设计默认(design pattern)

  • 软件开发过程中面临的一般问题的解决方案
  • 反复使用的、多数人知晓的、经过分类编目、代码设计经验的 总结
  • 利于重用代码、提高代码可读性、可靠性

the_relationship_between_design_patterns

设计模式原则

设计模式主要基于以下面向对象设计原则

  • 对接口编程而不是对实现编程
  • 优先使用对象组合而不是继承

六大原则

  • 开闭原则(open close principle)

    • 对扩展开放、修改关闭。
    • 程序需要扩展时,不能修改原有代码以实现热拔插效果, 易于扩展和升级
  • 里氏代换原则(Liskov Substitution Principle)

    • 任何基类可以出现的地方,子类一定可以出现
    • 此为继承复用的基石,只有派生类了可以替换掉基类,且 软件功能不受到影响,基类才能真正被复用
    • 对开闭原则的补充,实现开闭原则的关键步骤就是 抽象化,基类与子类的继承关系就是抽象化的具体实现, LSP就是对实现抽象化的具体步骤的规范
  • 依赖倒转原则(Dependence Inversion Principle)

    • 针对接口编程,以来抽象而不是具体
    • 开闭原则的基础
  • 接口隔离原则(Interface Segregation Principle)

    • 使用多个隔离的接口优于单个接口
    • 降低类之间的耦合度
  • 迪特米法则(最小知道原则,Demeter Principle)

    • 一个尸体应尽量少与其他实体发生相互作用
    • 使得系统功能模块相对独立
  • 合成复用原则(Composite Reuse Principle)

    • 尽量使用合成、聚合方式,而不是继承

创建型模式

在创建对象的同时隐藏创建逻辑的方式,而不是直接使用new 运算符直接实例化对象,这使得程序在判断针对某个给定实例需要 创建哪些对象时更加灵活

工厂模式 Factory Pattern

创建对象时不会对客户端暴露创建逻辑,而是通过共同的接口指向 新创建的对象

  • 意图:定义创建对象的接口,让其子类决定实例化何种工厂类, 工厂模式使其创建过程延迟到子类进行
  • 解决问题:接口选择问题
  • 使用场景:明确地计划不同条件下创建不同实例时
  • 解决方案:子类实现工厂接口,同时返回抽象产品
  • 关键:创建过程在子类中进行
  • 优点
    • 只需要名称就可以创建对象
    • 扩展性高,需要增加产品,只需要增加工厂类
    • 屏蔽产品具体实现,调用者只关心产品接口
  • 缺点
    • 每次增加产品,都需要增加具体类和对象实现工厂,系统中 类个数增长快,增加复杂度
    • 增加了系统具体类的依赖
  • 注意
    • 在任何需要生成复杂对象的地方,都可以使用工厂模式
    • 但简单对象,使用工厂模式需要引入工厂类,增加系统 复杂度
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
// step1: create interface
public interface Shape{
void draw();
}

// step2: create concrete class implementing interface
public class Rectangle implements Shape{
@Override
public void draw(){
System.out.println('Inside Rectangle::draw() method.");
}
}
public class Square implements Shape{
@Override
public void draw(){
System.out.println("Inside Square::draw() method.");
}
}
public class Circle implements Shape{
@Override
public void draw(){
System.out.println("Inside Circle::draw() method.");
}
}

// step3: create factory class generating concrete classes
// according to given info
public class ShapeFactory{
public Shape getShape(String shapeType){
if (shapeType == null){
return null;
}
if (shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
}
if (shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
}
if (shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}

// usage demo
public class FactoryPatternDemo{
public static void main(String[] args){
ShapeFactory shapeFactory = new ShapeFactory();

Shape circle = shapeFactory.getShape("CIRCLE");
circle.draw();
Shape rectangle = shapeFactory.getShape("Rectangle");
rectangle.draw();
Shape square = shapeFactory.getShape("square");
square.draw();
}
}

抽象工厂模式 Abstract Factory Pattern

围绕一个超级工厂创建其他工厂,超级工厂又称为其他工厂的工厂, 其中接口是负责创建相关对象的工厂,不需要显式指定他们的类, 每个生成的工厂都能按照工厂模式提供对象

  • 意图:提供创建一系列相关或相互依赖对象的接口,且无需指定 其具体类型
  • 解决问题:端口选择问题
  • 使用场景:系统的产品有多个产品族,而系统只需要消费其中 一族的产品
  • 解决方案:在一个产品族中定义多个产品
  • 优点:当一个产品族中多个对象被设计为一起工作时,能够保证 客户端始终只使用同一个产品族中的对象
  • 缺点:产品族扩展困难,需要同时修改具体类、抽象工厂类
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// step1: create interface
public interface Shape{
void draw();
}
public interface Color{
void fill();
}

// step2: create concrete class implementing interface

// shape classes
public class Rectangle implements Shape{
@Override
public void draw(){
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square implements Shape{
@Override
public void draw(){
System.out.println("Inside Square::draw() method.");
}
}
public class Circle implements Shape{
@Override
public void draw(){
System.out.println("Inside Circle::draw() method.");
}
}

// color classes
public Red implements Color{
@Override
public void fill(){
System.out.println("Inside Red::fill() method.");
}
}
public Green implements Color{
@Override
public void fill(){
System.out.println("Inside Green::fill() method.");
}
}
public Blue implements Color{
@Override
public void fill(){
System.out.println("Inside Blue::fill() method.");
}
}

// step3: create abstract class to get factory classes
public abstract class AbstractFactory{
public abstract Color getColor(String color);
public abstract Shape getShape(String shape);
}

// step4: create factory classes extending abstract factory
// class
public class ShapeFactory extends AbstractFactory{
@Override
public Shape getShape(String shapeType){
if (shapeType == null){
return null
}
if (shapeType.equalsIgnoreCase("CIRCLE"){
return new Circle();
}
if (shapeType.equalsIgnoreCase("Rectangle"){
return new Rectangle();
}
if (shapeType.equalsIgnoreCase("square"){
return new Square();
}
return null;
}

@Override
public Color getColor(String Color){
return null;
}
}

public class ColorFactory extends AbstractFactory{
@Override
public Shape getShape(String shapeType){
return null;
}

@Override
public getColor(String color){
if (color == null){
return null;
}
if (color.equalsIgnoreCase("RED"){
return new Red();
}
if (color.equalsIgnoreCase("Green"){
return new Green();
}
if (color.equalsIgnoreCase("blue"){
return new Blue();
}

return null;
}
}

// step5: create factory producer
public class FactoryProducer{
public static AbstractFactory getFactory(String choice){
if(choice.equalsIgnoreCase("SHAPE"){
return new ShapeFactory();
}else if(choice.equalsIgnoreCase("COLOR"){
return new ColorFactory();
}
return null;
}
}

// step6: demo
public class AbstractFactoryPatternDemo{
public static void main(String[] args){

// use shape cluster only
AbstractFactory shapeFactory = FactoryProducer.getFactory("SHPAE");
Shape circle = shapeFactory.getShape("CIRCLE");
circle.draw();
Shape rectangle = shapeFactory.getShape("RECTANGLE");
rectangle.draw();
Shape square = shapeFactory.getShape("SQUARE");
square.draw();

//use color cluster only
AbstractFactory colorFactory = FactoryProducer.getFactory("color");
Color red = colorFactory.getColor("RED");
red.fill();
Color green = colorFactory.getColor("green");
green.fill();
Color blue = colorFactory.getColor("blue");
blue.fill();
}
}

单例模式 Singleton Pattern

涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个 对象被创建,提供一种访问其唯一对象的方式,不需要实例化该类 的对象,可以直接访问

  • 意图:保证类只有一个实例,并提供一个访问其的全局访问点
  • 解决问题:一个全局使用的类频繁创建与销毁
  • 使用场景:控制实例数据,节省系统资源
  • 解决方法:判断系统是否已有实例,若有则返回,否则创建
  • 优点
    • 内存仅有一个实例,减少内存开销
    • 避免对资源的多重占用(如文件读写)
  • 缺点
    • 没有接口、无法继承,与单一职责冲突
    • 类应该只关心内部逻辑,而不设计外部如何实例化
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
// step1: create singleton class
public class SingleObject{

// create object as soon as class is loaded. It's okay
// to move creating to `getInstance()`
private static SingleObject instance = new SingleObject();

// private construction method, so this class won't be
// instantiated
private SingleObject(){};

public static SingleObject getInstance(){
return instance;
}

public void showMessage(){
System.out.println("hello world");
}
}

// step2: test demo
public class SingletonPatternDemo{
public static void main(String[] args){

// private construction method, so `new SingleObject()`
// is illegal
SingleObject object = SingleObject.getInstance();
object.showMessage();
}
}

建造者模式 Builder Pattern

使用多个简单对象一步步构建成复杂对象

  • 意图:将复杂的构建与其表示相分离,使得同样的构建过程可以 创建不同的表示
  • 解决问题:对于由负责多个子对象组成复杂对象,其各个组成 部分可能随着需求而变化,而构建复杂对象的算法相对稳定
  • 使用场景:基本部件不变,而其组合经常变化
  • 解决方案:将变、不变分开考虑
  • 优点
    • 建造者独立,容易扩展
    • 便于控制细节风险 缺点
    • 产品必须有共同点,范围有限制
    • 如果内部变化复杂,会有很多建造类
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// step1
public interface Item{
public String name();
public Packing packing();
public float price();
}
public interface Packing{
public String pack();
}

// step2
public class Wrapper implements Packing{
@Override
public String pack(){
return "Wrapper";
}
}
public class Bottle implements Packing{
@Override
public String pack(){
return "Bottle";
}
}

// step3
public abstract class Burger implements Item(){
@Override
public Packing packing(){
return new Wrapper();
}
@Override
public abstract float price();
}
public abstract class ColdDrink implements Item{
@Override
public Packing packing(){
return new Bottle();
}
@Override
public abstract float price();
}

// step4
public class VegBurger extends Buger{
@Override
public float price(){
return 25.0f;
}
@Override
public String name(){
return "Veg Burger";
}
}
public class ChickenBurger extends Burger{
@Override
public float price(){
return 50.5f;
}
@Override
public String name(){
return "Chicken Burger";
}
}
public class Coke extends ColdDrink{
@Override
public float price(){
return 30.0f;
}
@Override
public String name(){
return "Coke";
}
}
public class Pepsi extends ColdDrink{
@Override
public float price(){
return 35.0f;
}
@Override
public String name(){
return "Pepsi";
}
}

// step5
import java.util.ArrayList;
import java.util.List;
public class Meal{
private List<Item> items = new ArrayList<Item>();
public void addItem(Item item){
items.add(item);
}
public float getCost(){
float cost = 0.0f;
for (Item item: items){
cost += item.price();
}
return cost;
}
public void showItem(){
for (Item item: items){
System.out.print("Item: " + item.name());
System.out.print(", Packing: " + item.packing().pack());
System.out.println(", Price: " + item.price());
}
}
}

// step6
public class MealBuilder{
public Meal prepareVegMeal(){
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
public Meal parpareNonVegMeal(){
Meal meal = new Meal();
meal.addItem(new ChickenBuger());
meal.addItem(new Pepsi());
return meal;
}
}

// step7
public class BuilderPatternDemo{
public static void main(String[] args){
MealBuilder mealBuilder = new MealBuilder();

Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total Cost: " + vegMeal.getCost());

Meal.nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost: " + nonVegMeal.getCost());
}
}

原型模式 Prototype Pattern

实现一个原型接口,用于创建重复对象,同时又能保证性能

  • 意图:用原型实例指定创建对象的种类,并通过拷贝这些原型 创建新的对象
  • 解决问题:在运行时建立和删除原型
  • 使用场景
    • 类的初始化代价大
    • 系统应该独立其产品的创建、构成、表示
    • 要实例化的类是在运行时指定
    • 避免创建与产品类层次平行的工厂类
    • 类的实例只能有几个不同的状态组合中的一种时,建立相应 数目的原型并克隆可能比每次用合适的状态手工实例化更 方便
    • 实际上原型模式很少单独出现,一般和工厂模式一起出现, 通过clone方法创建对象返回给调用者
  • 优点
    • 性能提高,尤其是直接创建对象代价比较大 (如对象需要在高代价的数据库操作后被创建)
    • 逃避构造函数的约束
  • 缺点
    • 配备克隆方法需要对类的功能全盘考虑,对已有类可能比较 难,尤其是类引用不支持串行化的间接对象
  • 注意:原型模式时通过拷贝现有对象生成新对象,要主要区分 浅拷贝和深拷贝
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// step1: create an abstract class implementing Cloneable
// interface
public abstract class Shape implements Cloneable{
// Java中已经同原型模式融为一体
// 实现`Cloneable`接口实现浅拷贝,深拷贝则是通过实现
// `Serializable`读取二进制流
private String id;
protected String type;

abstract void draw();
public String getType(){
return type;
}
public String getId(){
return id;
}
public void setId(String id){
this.id = id;
}
public Object clone(){
Object clone = null;
try{
clone = super.clone();
}
catch (CloneNotSupportedException e){
e.printStackTrace();
}
return clone;
}
}

// step2: create a concrete class extending the abstract
// class
public class Rectangle extends Shape{
public Rectangle(){
tpye = "Rectangle";
}
@Override
public void draw(){
System.out.println("Inside Rectangle::draw() method.");
}
}
public class Square extends SHape{
public Square(){
type = "Sqaure";
}
@Override
public void draw(){
System.out.println("Inside Square::draw() method.");
}
}
public class Circle extends Shape{
public Circle(){
type = "Circle";
}
@Override
public void draw(){
System.out.println("Inside Circle::draw() method.");
}
}

// step3: creat a class to store the implements
import java.util.Hashtable;
public class ShapeCache{
private static Hashtable<String, Shape> shapeMap
= new Hashtable<String, Shape>();

public static Shape getShape(String shapeId){
Shape cachedShape = shapeMap.get(shapeId);
return (Shape)cachedShape.clone();
}
public static void loadCache(){
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(), circle);

Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(), square);

Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(), rectangle);
}
}

// step4: test demo
public class PrototypePatternDemo{
public static void main(String[] args){
ShapeCache.loadCache();

Shape clonedShape = (Shape)ShapeCache.getShape("1");
System.out.println("shape: " + clonedShape.getType());
Shape clonedShape2 = (Shape)ShapeCache.getShape("2");
System.out.println("shape: " + clonedShape2.getType());
Shape clonedShape3 = (Shape)ShapeCache.getShape("2");
System.out.println("shape: " + clonedShape3.getType());
}
}

结构型模式

关注类和对象的;组合,继承的概念被用来组合接口和定义组合对象 获得新功能的方式

适配器模式 Adaptor Pattern

不兼容的接口之间的桥梁,负责加入独立的或不兼容的接口功能

  • 意图:将一个类的接口转换为客户希望的另一个接口,使得原本 由于接口不兼容的类可以一起工作
  • 解决问题:“现存对象”无法满足新环境要求
  • 使用场景
    • 系统需要使用现有类,现有类不符合系统需要
    • 想要建立可以重复使用的类,用于与一次彼此之间没有太大 关联的类(包括可能未来引进的类)
    • 通过接口转换将一个类插入另一个类
  • 解决方法:继承或以来
  • 优点
    • 可以让任何两个没有关联的类一起运行
    • 提高类类的复用
    • 增加了类的透明度
    • 灵活性好
  • 缺点
    • 过多的使用适配器可能会导致系统凌乱,不易把握整体
    • 对于单继承语言,至多只能适配一个适配器类
  • 注意事项:适配器模式用于解决正在服役的项目问题,而不是 详细设计时
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
68
69
70
71
72
73
74
75
76
77
78
79
80
// step1: create interfaces
public interface MediaPlayer{
public void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer{
public void playVlc(String fileName);
public void playMp4(String fileName);
}

// step2:
public class VlcPlayer implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName){
System.out.println("Playing vlc file. Name: " + fileName);
}
@Override
public void playMp4(String fileName){
}
}
public class Mp4Player implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName){
}
@Override
public void playMp4(String fileName){
System.out.println("Playing mp4 file. Name: " + fileName);
}
}

// step3: create adapter class
public class MediaAdapter implements MediaPlayer{
AdvancedMediaPlayer advancedMusicPlayer;

// construction method
public MediaAdapter(String audioType){
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer = new VlcPlayer();
}else if (audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName){
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer.playVlc(fileName);
}else if(audioType.equalsIgnoreCase("mp4")){
advancedMusciPlayer.playMp4(fileName);
}
}
}

// step4:
public class AudioPlayer implements MediaPlayer{
MediaPlayer mediaAdapter;

@Override
public void play(String audioType, String fileName){
if(audioType.equalsIgnoreCase("mp3")){
System.out.println("Playing mp3 file. Name: " + fileName);
}else if(audioType.equalsIgnoreCase("vlc") ||
audioType.equalsIgnoreCase("mp4")){
mediaAdapter = new MediaAdatper(audioType);
mediaAdapter.play(audioType, fileName);
}else{
System.out.println("Invalid media. " +
audioType + " format not supported.");
}
}
}

// step5: test demo
public class AdapterPatternDemo{
public static void main(String[] args){
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}

桥接模式 Bridge Pattern

把抽象化与现实化解耦,使得二者可以独立变化,涉及一个作为桥接 的接口,使得实体类的功能能独立与接口实现类

  • 意图:将抽象部分与实现部分分离,使得其可以独立变化
  • 解决问题:在有多种可能会变化的情况下,用继承会造成类爆炸 问题,扩展起来不灵活
  • 使用场景:实现系统可能有多个角度分类,每种角度都可能变化
  • 解决方法:把多角度分类分离出来,让其独立变化,减少其间 耦合
  • 优点
    • 抽象和实现的分离
    • 优秀的扩展能力
    • 实现细节对客户透明
  • 缺点:照成系统的理解与设计难度,由于聚合关联关系建立在 抽象层,要求开发者针对抽象进行设计、编程
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
// step1
public interface DrawAPI{
public void drawCircle(int radius, int x, int y);
}

// step2
public class RecCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y){
System.out.println("Drawing Circle[color: red, radius: "
+ radius + ", x: " + x +", y: " + y + "]");
}
}
public class GreenCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y){
System.out.println("Drawing Circle[color: Green, radius: "
+ radius + ", x: " + x +", y: " + y + "]");
}
}

// step3
public abstract class Shape{
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw();
}

// step4
public class Circle extends Shape{
private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI){
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
public void draw(){
drawAPI.drawCircle(radius, x, y);
}
}

// step5: test demo
public class BridgePatternDemo{
public static void main(String[] args){
Shape redCircle = new Circle(100, 100, 10, new RedCircle());
Shape greenCircle = new Circle(100, 100, 10, new GreenCircle());
redCircle.draw();
greenCircle.draw();
}
}

过滤器(标准)模式 Filter、Criteria Pattern

使用多种不同的标准过滤一组对象,通过逻辑运算以解耦的方式将其 连接起来,结合多个标准或者单一标准

  • 意图:
  • 解决问题:
  • 适用场景:
  • 解决方法:
  • 优点
  • 缺点
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// step1: create a class which will be impose criteria on
public class Person{
private String name;
private String gender;
pirvate String maritalStatus;

public Person(String name, String gender, String maritalStatus){
this.name = name;
this.gender = gender;
this.maritalStatus = maritalStatus;
}
public String getName(){
return name;
}
public String getGender(){
return gender;
}
public String getMaritalStatus(){
return maritalStatus;
}
}

// step2: create an interface for criteria
import java.util.List;
public interface Criteria{
public List<Person> meetCriteria(List<Person> persons);
}

// step3: creat
import java.util.ArrayList;
import java.util.List;
public class CriteriaMale implements Criteria{
@Override
public List<Person> meetCriteria(List<Person> persons){
List<Person> malePersons = new ArrayList<Peron>();
for(Person person : persons){
if(person.getGender().equalsIgnoreCase("MALE"){
malePersons.add(Person);
}
}
return malePersons;
}
}
public class CriteriaFemale implements Criteria{
@Override
public List<Person> meetCriteria(List<Person> persons){
List<Person> femalePerson = new ArrayList<Person>();
for(Person person: persons){
if(person.getGender.equalsIgnoreCase("Female"){
femalePersons.add(person);
}
}
return femalePersons;
}
}
public class CriteriaSingle implements Criteria{
@Override
public List<Person> meetCriteria(List<Person> persons){
List<Person> singlePersons = new ArrayList<Person>();
for(Person person: persons){
if(person.getMaritalStatus().equalsIgnoreCase("Single")){
singlePerson.add(person);
}
}
return singlePersons;
}
}
public class AndCriteria implements Criteria{
pirvate Criteria criteria;
private Criteria otherCriteria;
public AndCriteria(Criteria criteria, Criteria otherCriteria){
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}

@Override
public List<Person> meetCriteria(List<Person> persons){
List<Person> firstCriteriaPersons = criteria.meetCriteria(persons);
return otherCriteria.meetCriteria(firstCriteriaPersons);
}
}
public class OrCriteria implements Criteria{
pirvate Criteria criteria;
private Criteria otherCriteria;
public Criteria(Criteria criteria, Criteria otherCriteria){
this.criteria = criteria;
this.otherCriteria = otherCriteria;
}

@Override
public List<Person> meetCriteria(List<Person> persons){
List<Person> firstCriteriaItems = criteria.meetCriteria(persons);
List<Person> otherCriteriaItems = criteria.meetCriteria(persons);
for(Person person: otherCriteriaItems){
if(!firstCriteriaItems.contains(person)){
firstCriteriaItems.add(person);
}
}
return firstCriteriaItems;
}
}

// step4: test demo
import java.util.ArrayList;
import java.util.List;
public class CriteriaPatternDemo{
public static void main(String[] args) {
List<Person> persons = new ArrayList<Person>();

persons.add(new Person("Robert","Male", "Single"));
persons.add(new Person("John","Male", "Married"));
persons.add(new Person("Laura","Female", "Married"));
persons.add(new Person("Diana","Female", "Single"));
persons.add(new Person("Mike","Male", "Single"));
persons.add(new Person("Bobby","Male", "Single"));

Criteria male = new CriteriaMale();
Criteria female = new CriteriaFemale();
Criteria single = new CriteriaSingle();
Criteria singleMale = new AndCriteria(single, male);
Criteria singleOrFemale = new OrCriteria(single, female);

System.out.println("Males: ");
printPersons(male.meetCriteria(persons));

System.out.println("\nFemales: ");
printPersons(female.meetCriteria(persons));

System.out.println("\nSingle Males: ");
printPersons(singleMale.meetCriteria(persons));

System.out.println("\nSingle Or Females: ");
printPersons(singleOrFemale.meetCriteria(persons));
}

public static void printPersons(List<Person> persons){
for (Person person : persons) {
System.out.println("Person : [ Name : " + person.getName()
+", Gender : " + person.getGender()
+", Marital Status : " + person.getMaritalStatus()
+" ]");
}
}
}

组合模式 Composite Pattern

把一组相似的对象当作一个单一的对象,依据树形结构来组合对象, 用来表示部分和整体层次,亦称部分整体模式

  • 意图:将对象组合成树形结构以表示“部分-整体”层次结构, 使得用户对单个对象和组合对象的使用具有一致性
  • 解决问题:模糊树形结构问题中简单元素和复杂元素的概念, 客户程序可以像处理简单元素一样处理复杂元素,使得客户程序 与复杂元素的内部结构解耦
  • 使用场景
    • 表示对象的“部分-整体”层次结构(树形结构)
    • 希望用户忽略组合对象与单个对象的不同,统一地使用组合 结构中所有对象
  • 解决方法:树枝和叶子实现统一接口,树枝内部组合该接口
  • 优点
    • 高层模块调用简单
    • 节点自由度增加
  • 缺点:叶子和树枝的声明都是实现类,而不是接口,违反了依赖 倒置原则
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
// step1: create a class containing a list of Self
import java.util.ArrayList;
import java.util.List;
public class Employee{
pirvate String name;
pirvate String dept;
private int salary'
private List<Employee> subordinates;

public Employee(String name, String dept, int sal){
this.name = name;
this.dept = dept;
this.salary = sal;
subordinates = new ArrayList<Employee>();
}
public void add(Employee e){
subordinates.add(e);
}
public void remove(Employee e){
subordinates.remove(e);
}
public List<Employee> getSubordinates(){
return subordinates;
}
public String toString(){
return ("Employee: [Name: " + name + ", dept: "
+ dept + ", salary: " + salary + "]");
}
}

// step2: test demo
public class CompositePatternDemo{
public static void main(String[] args){
Employee CEO = new Employee("John", "CEO", 30000);
Employee headSales = new Employee("Robert", "Head Sales", 20000);
Employee headMarketing = new Employee("Michel", "Head Marketing", 20000);
Employee clerk1 = new Employee("Laura", "Marketing", 10000);
Employee clerk2 = new Employee("Bob", "Marketing", 10000);
Employee saleExecutive1 = new Employee("Richard", "Sales", 10000);
Employee saleExecutive2 = new Employee("Rob", "Sales", 10000);
CEO.add(headSales);
CEO.add(headMarketing);
headSales.add(saleExecutive1)
headSales.add(saleExecutive2);
headMarketing.add(clerk1);
headMarketing.add(clerk2);

System.out.println(CEO);
for(Employee headEmployee: CEO.getSubordinates()){
System.out.println(headEmployee);
for(Employee employee: headEmployee.getSubordinates()){
System.out.println(employee);
}
}
}
}

组合模式:在对象中包含其他对象,就是树,类似于叶子节点等等

装饰器模式 Decorator Pattern

创建一个新类用于包装原始类,向其添加新功能,同时不改变其结构

  • 意图:动态的给对象添加额外的职责,比生成子类更灵活
  • 解决问题:为扩展类而使用继承的方式,会为类引入静态特征, 随着扩展功能的增多,子类会膨胀
  • 使用场景:不增加很多子类的情况下扩展类
  • 解决方法:将具体功能职责划分,同时继承装饰者模式
  • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,替代 继承从而动态的扩展实现类的功能
  • 缺点:多层装饰复杂
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
// step1
public interface Shape{
void draw();
}

// step2
public class Rectangle implements Shape{
@Override
public void draw(){
System.out.println("Shape: Rectangle");
}
}
public class Circle implements Shape{
@Override
public void draw(){
System.out.println("Shape: Cirlce");
}
}

// step3
public abstract class ShapeDecorator implements Shape{
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}

// step4
public class RedShapeDecorator extends ShapeDecorator{
public RedShapeDecorator(Shape decoratedShape){
super(decoratedShape);
}
@Override
public void draw(){
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}

// step5: demo
public class DecoratorPatternDemo{
public static void main(String[] args){
Shape circle = new Circle();
Shape redCircle = new RedShapeDecorator(new Circle());
Shape redRectangle = new RedShapeDecorator(new Rectangle());
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}

外观模式 Facade Pattern

隐藏系统的复杂性,想客户端提供访问系统的接口,简化客户端请求 方法和对象系统类方法的委托调用

  • 意图:定义一个高级接口,使得子系统更加容易使用
  • 解决问题:降低访问复杂系统内部子系统时的复杂度
  • 使用场景
    • 客户端不需要知道复杂系统内部的复杂联系,系统只需要 提供“接待员”
    • 定义系统的入口
  • 解决方法:客户端不与系统耦合,外观类与系统耦合
  • 优点
    • 减少系统相互依赖
    • 提高灵活性
    • 提高安全性
  • 缺点:不符合开闭原则,修改时麻烦
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
// step1
public interface Shape{
void draw();
}
// step2
public class Rectangle implements Shape{
@Override
public void draw(){
System.out.println("Rectangle::draw()");
}
}
public class Square implements Shape{
@Override
public void draw(){
System.out.println("Square::draw()");
}
}
public class Circle implements Shape{
@Override
public void draw(){
System.out.println("Circle::draw()");
}
}
// step3
public class ShapeMaker{
private Shape circle;
private Shape rectangle;
private Shape square;

public ShapeMaker(){
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}
// step4
public class FacadePatternDemo{
public static void main(String[] args){
ShapeMaker shapeMaker = new ShapeMaker();
shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();
}
}

享元模式 Flyweight Pattern

尝试重用现有的同类对象,仅在找不到匹配对象才创建新对象, 减少创建对象的数量、内存占用,提高性能

  • 意图:运用共享技术有效地支持大量细粒度的对象
  • 解决问题:大量对象时,抽象出其中共同部分,相同的业务请求 时,直接返回内存中已有对象
  • 使用场景
    • 系统中有大量对象,消耗大量内存
    • 对象的状态大部分可以外部化
    • 对象可以按照内蕴状态分组,将外蕴状态剔除后可以每组 对象只使用一个对象代替
    • 系统不依赖这些对象的身份,对象是“不可分辨的”
  • 解决方法:用唯一标识码判断,如果内存中存在,则返回唯一 标识码所标识的对象
  • 优点:减少对象创建、内存消耗
  • 缺点:增加系统复杂度,需要分离内蕴、外蕴状态,而且外蕴 状态具有固有化相知,不应该随着内部状态变化而变化,否则会 照成系统混乱
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
68
69
70
71
// step1
public interface Shape{
void draw();
}

// step2
public class Circle implements Shape{
private String color;
private int x;
private int y;
private int radius;

public Circle(String color){
this.color = color;
}
public void setX(int x){
this.x = x;
}
public vodi setY(int y){
this.y = y;
}
public void setRadius(int radius){
this.radius = radius;
}
@Override
public void draw(){
System.out.println("Circle: Draw() [Color: " + color
+ ",x: " + x + ",y: " + y + ",radius: " + radius);
}
}

// step3
import java.util.HashMap;
public class ShapeFactory{
private static final HashMap<String, Shape> circleMap = new HashMap<>();

public static Shape getCircle(String color){
Circle circle = (Circle)circleMap.get(Color);
if(circle == null){
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color: " + color);
}
return cirle;
}
}

// step4
public class FlyweightPatternDemo{
pirvate static final String colors[] =
{"Red", "Green", "Blue", "White", "Black"};

public static void main(String[] args){
for (int i = 0; i < 20; ++i){
Circle circle = (Circle)ShapeFactory.getCircle(getRamdonColor());
circle.setX(getRandomX());
circle.setY(getRandomY());
circle.setRadius(100);
circle.draw();
}
}
private static String getRandomColor(){
return colors[(int)(Math.random()*colors.length))];
}
private static int getRandomX(){
return (int)(Math.random()*100)
}
private static int getRandomY(){
return (int)(Math.random()*100);
}
}

代理模式 Proxy Pattern

创建具有现有对象的对象,向外界提供功能接口,一个类代表另 一个类的功能,

  • 意图:为其他对象提供一种代理以控制对这个对象的访问
  • 解决问题:直接访问对象会给使用者、系统结构代理问题
  • 使用场景:在访问类时做一些控制
  • 解决方法:增加中间层
  • 优点
    • 职责清晰
    • 高扩展性
    • 智能化
  • 缺点
    • 在客户端和真实端之间增加了代理对象,可能降低效率
    • 代理模式需要额外工作,实现可能非常复杂
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
// step1
public interface Image{
void dispaly();
}

// step2
publi class RealImage implements Image{
private String fileName;

public RealImage(String fileName){
this.filName = fileName;
loadFromDisk(fileName);
}
@Ovrride
public void display(){
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}

// step3
public class ProxyImage implements Image{
private RealImage realImage;
private String fileName;

public ProxyImage(String fileName){
this.fileName = fileName;
// it won't load image until display-method is
// called
}
@Override
public void display(){
if(realImage = Null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}

// step4
public class ProxyPatternDemo{
public static void main(String[] args){
Image image = new ProxyImage("test_10mb.jpg");
image.display();
}
}

行为型模式

特别关注对象之间的通信

责任链模式 Chain of Responsibility Pattern

为请求创建接收者对象的链,对请求的发送者和接收者进行解耦, 通常每个接着者都包含对另一个接收者的引用

  • 意图:避免请求发送者与接收者耦合,让多个对象都有可能接受 请求,将这些对象连接成职责链,并且沿着这条链传递请求, 直到有对象处理
  • 解决问题:职责链上处理者负责处理请求,客户只要将请求发送 到职责链上即可,无需关心请求的处理细节和请求的传递,所以 职责链将请求的发送者和请求的处理者解耦
  • 使用场景:在处理时已过滤多次
  • 解决方法:拦截的类都实现统一接口
  • 优点
    • 降低耦合度
    • 简化对象,发送者不需要知道链的结构
    • 增强接收者的灵活性,通过改变、排序职责链内成员,允许 动态的增删责任
    • 增加新的请求处理类方便
  • 缺点
    • 不能保证请求一定被接收
    • 系统性能将受到影响,调试代码时不方便,可能造成循环 调用
    • 不容易观察运行时特征,有碍于除错
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
68
69
70
71
// step1
public abstract class AbstractLogger{
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3'
protected int level;
protected AbstractLogger nextLogger;

public void setNextLogger(AbstractLogger nextLogger){
this.nextLogger = nextLogger;
}
public void logMessage(int level, String Message){
if(this.level <= level){
write(message);
}
if(nextLogger != null){
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}

// step2
public class ConsoleLogger extends AbstractLogger{
public consoleLogger(int level){
this.level = level;
}
@Override
protected void write(String message){
System.out.println("Standard Console::Logger: " + message);
}
}
public class ErrorLogger extends AbstractLogger{
public ErrorLogger(int level){
this.level = level;
}
@Override
protected void write(String message){
System.out.println("Error Console::Logger: " + message);
}
}
public class FileLogger extends AbstractLogger{
public FileLogger(int level){
this.level = level;
}
@Override
protected void write(String message){
System.out.println("File::Logger: " + message);
}
}

// step3
public class ChainPatternDemo{
private static AbstractLogger getChainOfLogger(){
AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG):
AbstractLogger console.Logger = new ConsoleLogger(AbstractLogger.INFO0;
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args){
AbstractLogger loggerChain = getChainLoggers();
loggerChain.logMessage(AbstractLogger.INFO,
"this is an information");
loggerChain.logMessage(AbstractLogger.DEBUG,
"this is a debug level infomation");
loggerChain.logMessage(AbstractLogger.ERROR,
"this is an error infomation");
}
}

命令模式 Command Pattern

请求以命令的形式包裹对象中,并传给调用对象,调用对象寻找可以 处理该命令的合适的对象,是一种数据驱动的设计模式

  • 意图:将请求封装成一个对象,用不同的请求对客户进行参数化
  • 解决问题:行为请求者、实现者通常是紧耦合关系,
  • 使用场景:需要对行为进行记录、撤销、重做、事务等处理时, 紧耦合关系无法抵御变化
  • 解决方法:通过调用者调用接收者执行命令
  • 优点
    • 降低系统耦合度
    • 容易添加新命令至系统
  • 缺点:使用命令模式可能会导致系统有过多具体命令类
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
// step1
public interface Order{
void execute();
}

// step2
public class Stock{
private String name = "ABC";
private int quantity = 10;
public void buy(){
System.out.println("Stock [Name: " + name
+ ", Quantity: " + quantity + "] bought");
}
public void seel(){
System.out.println("Stock [Name: " + name
+ ", Quantity: " + quantity + "] sold");
}
}

// step2
public class BuyStock implements Order{
private Stock abcStock;
public BuyStock(Stock abcStock){
this.abcStock = abcStock;
}
public void execute(){
abcStock.but();
}
}
public class SellStock implements Order{
private Stock abcStock;
public SellStock(Stock abcStock){
this.abcStock = abcStock;
}
pubic void execute(){
abcStock.sell();
}
}

// step3
import java.util.ArrayList;
import java.util.List;
public class Broker{
private List<Order> orderList = new ArrayList<Order>();
public void takeOrder(Order order){
orderList.add(order);
}
public void placeOrders(){
for(Order order: orderList){
order.execute();
}
orderList.clear();
}
}

// step4
public class CommandPatternDemo{
public static void main(String[] args){
Stock abcStock = new Stock();
BuyStock buyStockOrder = new BuyStock(abcStock);
SellStock sellStockOrder = new SellStock(abcStock);
Broker broker = new Broker();
broker.takeOrder(buyStockOrder);
broker.takeOrder(sellStockOrder);
broker.placeOrders();
}
}

解释器模式 Interpreter Pattern

实现一个表达式接口用于解释特定上下文,提供了评估语言的语法 或表达式的方式

  • 意图:定义给定语言的文法表示、解释器
  • 解决问题:为固定文法构建解释句子的解释器
  • 使用场景:若特定类型问题发生频率足够高,可能值得将其各 实例表述为监督语言的句子,便可构建解释器解释这些句子来 解决问题
  • 解决方法;构建语法树,定义终结符、非终结符
  • 优点
    • 扩展性好,灵活
    • 增加了新的解释表达式的方式
    • 容易实现简单文法
  • 缺点
    • 适用场景少
    • 对于复杂文法维护难度大
    • 解释器模式会引起类膨胀
    • 解释器模式采用递归调用方法
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
// step1
public interface Expression{
public boolean interpret(String context);
}

// step2
public class TerminalExpression implements Expression{
private String data;
public TerminalExpression(String data){
this.data = data;
}
@Overide
public boolean interpret(String context){
if(context.contain(data)){
return true;
}
return false;
}
}
public class OrExpression implements Expression{
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2){
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context){
return expr1.interpret((context) || expr2.interpret(context);
}
}
public class AndExpression implements Expression{
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2){
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context){
return expr1.interpret(context) && expr2.interpret(context);
}
}

// step3
public class InterpreterPatternDemo{
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}
public static void main(String[] args){
Expression isMale = getMaleExpression();
Expression isMarriedWoman = getMarriedWomanExpression();
System.out.println("John is male?" + isMale.interpret("John");
System.out.println("Julie is a married women?"
+ is MarriedWoman.interpret("Married Julie"));
}
}

迭代器模式 Iterator Pattern

用于访问顺序访问剂盒对象的元素,不需要知道集合对象的底层表示

  • 意图:提供方法用于顺序访问聚合对象中的各个元素,又无须 暴露该对象的内部表示
  • 解决问题:不同方式遍历整个整合对象
  • 使用场景:遍历聚合对象
  • 解决方法:把在元素中游走的责任交给迭代器
  • 优点
    • 支持以不同的方式遍历对象
    • 迭代器简化了聚合类
    • 增加新的聚合类、迭代器类方便
  • 缺点:会将存储数据和遍历数据的职责分离,增加新的聚合类 需要增加新的迭代器类,类数目成对增加,增加系统复杂性
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
// step1
public interface Iterator{
public boolean hasNext();
public Object next();
}
public interface Container{
public Iterator getIterator();
}

// step2
public class NameRepository implements Container{
public String names[] = {"Robert", "John", "Julie", "Lora"};
@Override
public Iterator getIterator(){
return new NameIterator();
}
private class NameIterator implements Iterator{
int index;
@Override
public boolean hasNext(){
if(index < names.length){
return true;
}
return false;
}
@Override
public Object Next(){
if(this.hasNext()){
return names[index++];
}
return null;
}
}
}

// step3
public class IteratorPatternDemo{
public static void main(String[] args){
NameReppository namesRepository = new NameRepository();
for(Iterator iter = namesRepository.getIterator(); iter.hasNext;){
String name = (String)iter.next();
System.out.println("Name: " + name);
}
}
}

中介者模式 Mediator Pattern

提供一个中介类处理不同类之间的通信,降低多个对象、类之间的 通信复杂性,支持松耦合,使代码便于维护

  • 意图:用一个中介对象封装一系列对象交互,中介使得个对象 不需要显式的相互引用,从而使其耦合松散,且可以独立地改变 它们之间地交互
  • 解决问题:对象之间存在大量的关联关系,会导致系统结构很 复杂,且若一个对象发生改变,需要跟踪与之关联的对象并作出 相应处理
  • 使用场景:多个类相互耦合形成网状结构
  • 解决方法:将网状结构分离为星形结构
  • 优点
    • 降低类的复杂度,多对多转为一对一
    • 各个类之间解耦
    • 符合迪米特原则
  • 缺点:中介者可能会很庞大、难以维护
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
// step1
import java.util.Date;
public class ChatRoom{
public static void showMessage(User user, String message){
System.out.println(new Date().toString()
+ "[" + use.getName() + "]" + message);
}
}

// step2
public class User{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public User(String name){
this.name = name;
}
public void sendMessage(String message){
ChatRoom.showMessage(this, message);
}
}

// step3
public class MediatorPatternDemo{
public static void main(String[] args){
User robert = new User("Robert");
User john = new User("John");
robert.sendMessage("Hi! John");
john.SendMessage("Hello! Robert.");
}
}

备忘录模式 Memento Pattern

保存对象地某个状态以便在适当的时候恢复对象

  • 意图:在不破坏封装性地前提下,捕获对象的内部状态并保存 于对象外
  • 解决问题:同意图
  • 使用场景:需要记录对象的内部状态,允许用户取消不确定或 错误的操作
  • 解决方法:通过备忘录类专门存储对象状态
  • 优点
    • 给用户提供了可以恢复状态的机制
    • 实现信息的封装,用户无需关心状态保存细节
  • 缺点:消耗资源,如果类成员变量过多会占用比较大的资源
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
// step2
public class Memento{
private String state;
public Memento(String state){
this.state = state;
}
public String getState(){
return state;
}
}

// step2
public class Originator{
private String state;
public void setState(String state){
this.state = state;
}
public String getState(){
reutrn state;
}
public Memento saveStateToMemento(){
return new Memento(state);
}
public vodi getStateFromMemento(Memento memento){
state = memento.getState();
}
}

// step3
import java.util.ArrayList;
import java.util.List;
public class CareTaker{
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento state){
mementoList.add(state);
}
public Memento get(int index){
return mementoList.get(index);
}
}

// step4
public class MementoPatternDemo{
public static void main(String[] args){
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State #1");
originator.setState("State #2");
careTaker.add(originator.saveStateToMemento());
originator.setState("State #3");
careTaker.add(Originator.saveStateToMemento());

System.out.println("Current State: " + originator.getState());
origiator.getStateFromMemento(careTaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getStateFromMemento(CareTaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}

观察者模式 Observer Pattern

  • 意图:定义对象间的一种一对多的依赖关系,档一个对象状态 发生改变时,所以依赖他的对象都得到通知并被自动更新
  • 解决问题:一个对象状态改变给其他对象通知的问题,需要考虑 易用性和低耦合,同时保证高度协作
  • 使用场景:同问题
  • 解决方法:使用面向对象技术将依赖关系弱化
  • 优点
    • 观察者和被观察者是抽象耦合的
    • 建立一套触发机制
  • 缺点
    • 如果被观察者有很多观察者,通过所有观察者耗费时间长
    • 观察者和被观察之间如果有循环依赖,观察目标可能会触发 循环调用,导致系统崩溃
    • 没有机制让观察者知道所观察的目标是如何发生变化成, 仅仅是知道观察目标发生了变化
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
68
69
70
71
72
73
74
75
76
77
78
79
80
// step1
import java.util.ArryaList;
import java.util.List;
public class Subject{
private List<Observer> observers
= new ArrayList<Observer>();
private int state;
public int getState(){
return state;
}
public void setState(){
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer){
observers.add(observer);
}
public void notifyAllObservers(){
for(Observer observer: observers){
observer.update();
}
}
}

// step2
public abstract class Observer{
protected Subject subject;
public abstract void update();
}

// step3
public class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update(){
System.out.println("Binary String: "
+ Interger.toBinaryString(subject.getState()));
}
}

public class OctalObserver extends Observer{
public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update(){
System.out.println("Octal String: "
+ Integer.toOctalString(subject.getState()));
}
}

public class HexaObserver extends Observer{
public HexaObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update(){
System.out.println("Hex String: "
+ Integer.toHexString(subject.getState()).toUpperCase());
}
}

// step4
public class ObserverPatternDemo{
public static void main(String[] args){
Subject subject = new Subject();
new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(16);
}
}

状态模式 State Pattern

创建表示各种状态的对象和一个行为随着状态改变而改变的context 对象

  • 意图:允许在对象的内部状态改变时改变它的行为,看起来好像 修改了其所属的类
  • 解决问题:对象的行为依赖其状态(属性),并且可以根据其 状态改变而改变其相关行为
  • 使用场景:代码中包含大量与对象状态相关的条件语句
  • 解决方法:将各种具体状态类抽象出来
  • 优点
    • 封装了转换规则
    • 枚举了可能状态,在枚举之前需要确定状态种类
    • 将所有与某个状态有关的行为放在一个类中,只需要改变 对象状态就可以改变对象行为,可以方便地增加新状态
    • 允许状态转换逻辑与状态对象合成一体
    • 可以让多个环境对象共享一个状态对象,减少系统中对象 数量
  • 缺点
    • 增加系统类和对象地个数
    • 结构与实现都较为复杂,使用不当将导致结构和代码的混乱
    • 对“开闭原则”支持不太好,增加新的状态类需要修改复杂 状态转换的源代码,修改某状态类的行为也需要修改对应类 的源代码
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
// step1
public interface State{
public void doAction(Context context);
}

// step2
public class StartState implements State{
public void doAction(Context context){
System.out.println("Player is in start state");
context.setState();
}
public String toString(){
return "Start State";
}
}
public class StopState implements State{
public void doAction(Context context){
System.out.println("Player is in the stop state");
context.setState(this);
}
public String toString(){
return "Stop State");
}
}

// step3
public class Context{
private State state;
public Context(){
state = null;
}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
}

// step4
public class StatePatternDemo{
public static void main(String[] args){
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
System.out.println(context.getState().toString());
StopState stopState = new StopState();
stopState.doAction(context);
System.out.println(context.getState().toString());
}
}

空对象模式 Null Object Pattern

创建一个指定各种要执行的操作的抽象类和扩展该类的实体类、一个 未做人实现的空对象类,空对象类将用于需要检查空值的地方取代 NULL对象实例的检查,也可以在数据不可用时提供默认的行为

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
// step1
public abstract class AbstractCustomer{
protected String name;
public abstract boolean isNil();
public abstract String getName();
}


// step2
public class RealCustomer extends AbstractCustomer{
public RealCustomer(String name){
this.name = name;
}
@Override
public String getName(){
return name;
}
@Override
public boolean isNil(){
return false;
}
}
public class NullCustomer extends AbstractCustomer{
@Override
public String getName(){
return "Not Available in Customer Database";
}
@Override
public boolean isNil(){
return true;
}
}

// step3
public class CustomerFactory(){
public static final String[] names = {"Rob", "Joe", "Julie"};
public static AbstractCustomer getCustomer(String name){
for(int i = 0; i < names.length; i++){
if(names[i].equalsIgnoreCase(name)){
return new RealCustomer(name);
}
}
return NullCustomer();
}
}

// step4
public class NullPatternDemo{
public static void main(String[] args){
AbstractCustomer customer1 = CustomerFactory.getCustomer("Rob");
AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob");
AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie");
AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura");
System.out.println("Customers");
System.out.println(customer1.getName());
System.out.println(customer2.getName());
System.out.println(customer3.getName());
System.out.println(customer4.getName());
}
}

策略模式 Strategy Pattern

创建表示各种策略的对象和一个随着策略对象改变的context对象, 类的行为、算法可以在运行时更改

  • 意图:定义一系列算法并封装,使其可以相互替换
  • 解决问题:有多种相似算法的情况下,使用if...else难以 维护
  • 使用场景:系统中有许多类,只能通过其直接行为区分
  • 解决方法:将算法封装成多个类,任意替换
  • 优点
    • 算法可以自由切换
    • 避免使用多重条件判断
    • 扩展性良好
  • 缺点
    • 策略类增多
    • 所有策略类都需要对外暴露
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
// step1
public interface Strategy{
public int doOperation(int num1, int num2);
}

// step2
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2){
return num1 + num2;
}
}
public class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2){
return num1 - num2;
}
}
public class OperationMultiply impliments Strategy{
@Override
public int doOperation(int num1, int num2){
return num1 * num2;
}
}

// step3
public class Context{
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}

// step4
public class StrategyPatternDemo{
public static void main(String[] args){
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}

模板模式 Template Pattern

一个抽象类公开定义执行它的方式(模板),其子类可以按需要重写 的方法实现,但将以抽象类中定义的方式调用

  • 意图:定义一个操作中算法的骨架,将一些步骤延迟到子类中, 使得子类可以不改变算法的接口即可重定义算法的某些步骤
  • 解决问题:一些方法通用,却在每个子类中重写该方法
  • 使用场景:有通用的方法
  • 解决方法:将通用算法抽象出来
  • 优点
    • 封装不变部分,扩展可变部分
    • 提取公共代码,便于维护
    • 行为由父类控制,子类实现
  • 缺点:每个不同的实现都需要子类实现,类的个数增加
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
// step1
public abstract class Game{
abstract void initializa();
abstract void startPlay();
abstract void endPlay();
public final void play(){
initialize();
startPlay();
endPlay();
}
}

// step2
public class Cricket extends Game{
@Override
void endPlay(){
Syste.out.println("Cricket Game Finished");
}
@Override
void initialize(){
System.out.println("Cricket Game Initialized! Start playing.");
}
@Override
void startPlay(){
System.out.println("Cricket Game Stared. Enjoy the game!");
}
}
public class Football extends Game{
@Override
void endPlay(){
Syste.out.println("Football Game Finished");
}
@Override
void initialize(){
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay(){
System.out.println("Football Game Stared. Enjoy the game!");
}
}

// step3
public class TemplatePatternDemo{
public static void main(String[] args){
Game game = new Cricket();
game.play();
System.out.println();
game = new Football();
game.play();
}
}

访问者模式 Visitor Pattern

使用一个访问者类,其改变元素类的执行算法

  • 意图:将数据结构与数据操作分离
  • 解决问题:稳定的数据结构和易变的操作耦合问题
  • 使用场景:需要对数据结构进行很多不同且不相关的操作,且要 避免让这些操作“污染”数据结构类
  • 解决方法:在数据结构类中增加对外提供接待访问者的接口
  • 优点
    • 符合单一职责原则
    • 优秀的扩展性、灵活性
  • 缺点
    • 具体元素对访问者公布细节,违反迪米特原则
    • 具体元素变更困难
    • 违反依赖倒置原则,依赖具体类而不是抽象类
    • 依赖递归,如果数据结构嵌套层次太深可能有问题
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
68
69
70
71
72
73
74
75
// step1
public interface ComputerPart{
public void accept(ComputerPartVisitor computerPartVsistor);
}

// step2
public class Keyboard implements ComputerPart{
@Override
public void accept(ComputerPartVisitor computerPartVisiter){
computerPartVisitor.visit(this);
}
}
public class Monitor implements ComputerPart{
@Override
public void accept(ComputerPartVisitor computerPartVisitor){
computerPartVisitor.visit(this);
}
}
public class Mouse implements ComputerPart{
@Override
public void accept(ComputerPartVisitor computerPartVisitor){
computerPartVisitor.visit(this);
}
}
public class Computer implements ComputerPart{
ComputerPart[] parts;
public Computer(){
parts = new ComputerPart[]{new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor computerPartVisitor){
for(ComputerPart part: parts){
part.accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}
}

// step3
public interface ComputerPartVisitor(){
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}

// step4
public class ComputerDisplayVisitor implements ComputerPartVisitor{
@Override
public void visit(Computer computer){
System.out.println("Display Computer");
}
@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}

@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}

@Override
public void visit(Monitor monitor) {
System.out.println("Displaying Monitor.");
}
}

// step5
public class VisitorPatternDemo{
public static void main(String[] args){
Computer computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}

J2EE模式

特别关注表示层,由Sun Java Center鉴定

MVC模式 MVC Pattern

Model-View-Controller模式,用于应用程序的分层开发

  • Model(模型):存取数据的对象,可以带有逻辑,在数据变化 时更新控制器
  • View(视图):模型包含的数据可视化
  • Controller(控制器):作用与模型、视图上,控制数据流向 模型对象,并在数据变化时更新视图,使视图与模型分离
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
68
69
// step1
public class Student{
private String rollNo;
private String name;
public String getRollNo(){
return rollNo;
}
public void setRollNo(String rollNo){
this.rollNo = rollNo;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}

// step2
public class StudentView{
public void printStudentDetails(String studentName, String studentRollNo){
System.out.println("Student: ");
System.out.println("Name: " + studentName);
System.out.println("Roll No: " + studentRollNo);
}
}

// step3
public class StudentController{
private Student model;
private StudentView view;
public StudentController(Student model, StudentView view){
this.model = model;
this.view = view;
}
public void setStudentName(String name){
model.setName(name);
}
public String getStudentName(){
return model.getName();
}
public void setStudentRollNo(String rollNo){
model.setRollNo(rollNo);
}
public String getStudentRollNo(){
return model.getRollNo();
}
public void updateView(){
view.printStudentDetails(model.getName(), model.getRollNo());
}
}

// step4
public class MVCPatternDemo{
public static void main(String[] args){
Student model = retriveStudentFromDatabase();
StudentView view = new StudentView();
StudentController controller = new StudentController(model, view);
controller.updateView();
controller.setStudentName("John");
controller.updateView();
}
private static Student retriveStudentFromDataBase(){
Student student = new Student();
student.setName("Robert");
student.setRollNo("10");
return student;
}
}

业务代表模式 Business Delegate Pattern

用于对表示层和业务层解耦,基本上是用来减少通信或对表示层代码 中的业务层代码的远程查询功能,业务层包括以下实体

  • Client(客户端)
  • Business Delegate(业务代表):为客户端提实体提供的入口 类,提供了对业务服务方法的访问
  • LookUp Service(查询服务):获取相关的业务实现,提供业务 对象对业务代表对象的访问
  • Business Service(业务服务):业务服务接口,实现其的具体 类提供了实际的业务实现逻辑
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
// step1
public interface BusinessService{
public void doProcessing();
}

// step2
public class EJBService implements BusinessService{
@Override
public void doProcessing(){
System.out.println("Processing task by invoking EJB Service");
}
}
public class JMSService implements BusinessService{
@Override
public void doProcessing(){
System.out.println("Processing task by invoking JMS Service");
}
}

// step3
public class BusinessLookUp{
public BusinessService getBusinessService(String serviceType){
if(serviceType.equalsIgnoreCase("EJB"){
return new EJBService();
}else{
returen new JMSService();
}
}
}

// step4
public class BusinessDelegate{
private BusinessLookUp lookupService = new BusinessLookUp();
private BusinessService businessService;
private String serviceType;
public void setServiceType(String serviceType){
this.serviceType = serviceType;
}
public void doTask(){
businessService = lookupService.getBusinessService(serviceTpye);
businessService.doProcessing();
}
}

// step5
public class Client{
BusinessDelegate businessService;
public Client(BusinessDelegate businessService){
this.businessServie = businessService;
}
public void doTask(){
businessService.doTask();
}
}

// step6
public class BusinessDelegatePatternDemo{
public static void main(String[] args){
BusinessDelegate businessDelegate = new BusinessDelegate();
businessDelegate.setServiceType("EJB");
Client client = new Client(businessDelegate);
cliend.doTask();
businessDelegate.setServiceType("JMS");
client.doTask();
}
}

组合实体模式 Composite Entity Pattern

一个组合实体是一个EJB实体bean,代表对象的图解,更新一个组合 实体时,内部依赖对象beans会自动更新,因为其是由EJB实体bean 管理的,组合实体bean参与者包括

  • Composite Entity(组合实体):主要的实体bean,可以是 粗粒的,或者包含一个粗粒度对象,用于持续生命周期
  • Coarse-Grained Object(粗粒度对象):包含依赖对象,有 自己的生命周期,也能管理依赖对象的生命周期
  • Dependent Object(依赖对象):持续生命周期依赖于粗粒度 对象的对象
  • Strategies(策略):如何实现组合实体
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
// step1
public class DependentObject1{
private String data;
public void setData(String data){
this.data = data;
}
public String getData(){
return data;
}
}
public class DependentObject2{
private String data;
public void setData(String data){
this.data = data;
}
public String getData(){
return data;
}
}

// step2
public class CoarseGrainedObject{
DependentObject1 do1 = new DependentObject1();
DependentObject2 do2 = new DependentObject2();
public void setData(String data1, String data2){
do1.setData(data1);
do2.setData(data2);
}
public String[] getData(){
return new String[] {do1.getData(), do2.getData()};
}
}

// step3
public class CompositeEntity{
private CoarseGrainedObject cgo = new CaorseGraiendObject();
public void setData(String data1, String data2){
cgo.setData(data1, data2);
}
public String[] getData(){
return cgo.getData();
}
}

// step4
public class Client{
private CompositeEntity compositeEntity = new CompositeEntity();
public void printData(){
for(String str: compositeEntity.getData()){
System.out.println("Data: " + str);
}
}
public void setData(String data1, data2){
compositeEntity.setData(data1, data2);
}
}

// step5
public class CompositeEntityPatternDemo{
public static void main(String[] args){
Client client = new Client();
client.setData("test", "data");
client.printData();
client.setData(second test", "data2");
client.printData();
}
}

数据访问对象模式 Data Access Object Pattern

用于把低级的数据访问API、操作从高级的业务服务中分离出来

  • Data Access Object Interface(数据访问对象接口):定义 在模型对象上要执行的标准操作
  • Data Access Object concrete class(数据访问对象实体类) :实现上述接口,负责从数据源(数据库、xml等)获取数据
  • Model/Value Object(模型/数值对象):简单的POJO,包含 get/set方法存储使用DAO类检索到的数据
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// step1
public class Student{
private String name;
private int rollNo;
Student(String name, int rollNo){
this.name = name;
this.rollNo = rollNo;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public int getRollNo(){
return rollNo;
}
public void setRollNo(int rollNo){
this.rollNo = rollNo;
}
}

// step2
import java.util.List;
public interface StudentDao{
public List<Student> getAllStudents();
public Student getStudent(int rollNo);
public void updateStudent(Student student);
public void deleteStudent(Student student);
}

// step3
import java.util.ArrayList;
import java.util.List;
public class StudentDaoImpl implements StudentDao{
List<Student> students;
public StudentDaoImpl(){
students = new ArrayList<Student>();
Student student1 = new Student("Roberts", 0);
Student student2 = new Student("John", 1);
students.add(student1);
students.add(student2);
}
@Override
public void deleteStudent(Student student){
students.remove(student.getRollNo());
System.out.println("Student: Roll No "
+ student.getRollNo()
+ ", deleted from database"
);
}
@Override
public List<Student> getAllStudents(){
return students;
}
@Override
public Student getStudent(int rollNo){
return students.get(rollNo);
}
@Override
public void updateStudent(Student student){
students.get(student.getRollNo()).setName(student.getName());
System.out.println("Student: Roll No "
+ student.getRollNo()
+ ", updated in the database"
);
}
}

// step4
public class DaoPatternDemo{
public static void main(String[] args){
StudentDao studentDao = new StudentDaoImpl();

for(Student student: studentDao:getAllStudents()){
System.out.println("Student: [RollNo: "
+ student.getRollNo()
+ ", Name: "
+ student.getName()
+ " ]"
);
}
Student student = studentDao.getAllStudents().get(0);
student.setName("Micheal");
studentDao.updateStudent(student);
studentDao.getStudent(0);
System.out.println("Student: [RollNo: "
+ student.getRollNo()
+ ", Name: "
+ student.getName()
+ "]"
);
}
}

前端控制器模式 Front Controller Pattern

用于提供一个集中请求处理机制,所有的请求都将由单一的处理程序 处理,该处理程序可以做认证、授权、记录日志、跟踪请求,然后把 请求传给相应的处理程序,包含以下部分

  • Front Controller(前端控制器):处理应用程序所有类型请求 的单个处理程序
  • Dispatcher(调度器):前端控制器调用,用于调度请求到相应 的具体处理程序
  • View(视图):为请求而创建的对象
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
// step1
public class Homeview(){
public void show(){
System.out.println("Displaying Home Page");
}
}
public class StudentView{
public void show(){
System.out.println("Displaying Student Page");
}
}

// step2
public class Dispatcher{
private StudentView studentView;
private HomeView homeView;
public Dispatcher(){
studentView = new StudentView();
homeView = new HomeView();
}
public void dispatch(String request){
if(request.equalsIgnoreCase("StudENT")){
studentView.show();
}else{
homeView.show();
}
}
}

// step3
public class FrontController{
private Dispatcher dispatcher;
public FrontController(){
dispatcher = new Dispatcher();
}
private boolean isAuthenticUser(){
System.out.println("User is authenticated successfully.");
return true;
}
private void trackRequest(String request){
System.out.println("Page requested: " + request);
}
public void dispatchRequest(String request){
trackReqeust(reqeust);
if(isAuthenticUser()){
dispatcher.dispatch(request);
}
}
}

// step4
public class FrontControllerPatternDemo{
public static void main(String[] args){
FrontController frontController = new FrontController();
frontController.dispatchRequest("HOMe");
frontController.dispatchRequest("STUDENT");
}
}

拦截过滤器模式 Intercepting Filter Pattern

用于应用程序的请求或相应做一些预处理、后处理,定义过滤器, 并将其应用在请求上后再传递给实际目标应用程序,过滤器可以做 认证、授权、记录日志、跟踪请求

  • Filter(过滤器):在处理程序执行请求之前或之后执行某些 任务
  • Filter Chain(过滤器链):带有多个过滤器,并按定义的顺序 执行这些过滤器
  • Target:请求处理程序
  • Filter Manager(过滤管理器):管理过滤器和过滤器链
  • Client(客户端):向Target对象发送请求的对象
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// step1
public interface Filter{
public void execute(String request);
}

// step2
public class AuthenticationFilter implements Filter{
public void execute(String request){
System.out.println("Authenticating request: " + request);
}
}
public class DebugFilter implements Filter{
public void execute(String request){
System.out.println("request log: " + request);
}
}

// step3
public class Target{
public void execute(String request){
System.out.println("Executing request: " + request);
}
}

// step4
import java.util.ArrayList;
import java.util.List;
public class FilterChain{
private List<Filter> filters = new ArrayList<Filter>();
private Target target;
public void addFilter(Filter filter){
filters.add(filter);
}
public void execute(String request){
for(Filter filter: filters){
filter.execute(reqeust);
}
target.execute(request);
}
public vodi setTarget(Target target){
this.target = target;
}
}

// step5
public class FilterManager{
FilterChain filterChain;
public FilterManager(Target target){
filterChain = new FilterChain();
filterChain.setTarget(target);
}
public void setFilter(Filter filter){
filterChain.addFilter(filter);
}
public void filterRequest(String request){
filterChain.execute(request);
}
}

// step6
public class Client{
FilterManager filterManager;
public void setFilterManager(FilterManager filterManager){
this.filterManager = filterManager;
}
public void sendRequests(String request){
filterManager.filterRequest(request);
}
}

// step7
public class InterceptingFilterDemo{
public static void main(String[] args){
FilterManager filterManager = new FilterManager(new Target());
filterManager.setFilter(new AuthenticationFilter());
filterManager.setFilter(new DebugFilter());
Client client = new Client();
client.setFilterManager(filterManager);
client.sendRequest("HOME");
}
}

服务定位器模式 Service Locator Pattern

用于使用JNDI查询定位各种服务时,充分利用缓存技术减小为服务 查询JNDI的代价,首次请求服务时,服务定位器在JNDI中查找服务, 并缓存供之后查询相同服务的请求使用

  • Service(服务):实际处理请求的服务,其引用可以在JNDI 服务器中查到
  • Context:JNDI带有要查找的服务的引用
  • Service Locator(服务定位器):通过JNDI查找和缓存服务来 获取服务的单点接触
  • Cache(缓存):缓存服务引用以便复用
  • Client(客户端):通过ServiceLocator调用服务对象
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// step1
public interface Serivce{
public String getName();
public void execute();
}

// step2
public class Service1 implements Service{
public void execute(){
System.out.println("Executing Service1");
}
@Override
public String getName(){
return "Service1";
}
}
public class Service2 implements Service{
public void execute(){
System.out.println("Executing Service2"):
}
@Override
public String getName(){
return "Service2";
}
}

// step3
public class InitialContext{
public Object lookup(String jndiName){
if(jndiName.equalsIgnoreCase("SERVICE1"){
System.out.println("Looking up and creating a new Service1 object");
return new Service1();
}else if(jndiName.equalsIgnoreCase("service2"){
System.out.println("Looking up and creating a new Service2 object.");
return new Service2();
}
return null;
}
}

// step4
import java.util.ArrayList;
import java.uitl.List;
public class Cache{
private List<Service> services;
public Cache(){
services = new ArrayList<Service>();
}
public Service getService(String serviceName){
for(Service service: services){
if(service.getName().equalsIgnoreCase(serviceName)){
System.out.println("Returning cached "
+ serviceName
+ " object"
);
}
}
return null;
}
public void addService(Service newService){
boolean exists = false;
for(Service service: services){
if(service.getName().equalsIgnoreCase(newService.getName())){
exists = true;
}
}
if(!exist){
services.add(newService);
}
}
}

// step5
public class ServiceLocator{
private static Cache cache;
static{
cache = new Cache();
}
public static Service getService(String jndiName){
Service service = cache.getService(jndiName);
if(service != null){
return service;
}
InitialContext context = new InitialContext();
Service service1 = (Service)context.lookup(jndiName);
cache.addService(service1);
return service1;
}
}

// step6
public class ServiceLocatorPatternDemo{
public static void main(String[] args){
Service service = ServiceLocator.getService("Service1");
service.execute();
service = ServiceLocator.getService("Service2");
service.execute();
service = ServiceLocator.getService("Service1");
service.execute();
service = ServiceLocator.getService("Service1");
service.execute();
}
}

传输对象模式 Tranfer Object Pattern

用于从客户端向服务器一次性传递带有多个属性的数据。传输对象 (数值对象)是具有getter/setter方法的简单POJO类,可 序列化,因此可以通过网络传输,没有任何行为。服务器端业务类 通常从数据库读取数据填充POJO,并发送到客户端或按值传递; 客户端可以自行创建传输对象并传送给服务器,以便一次性更新 数据库中的数值

  • Business Object(业务对象):为传输对象填充数据的业务 服务
  • Transfer Object(传输对象):简单的POJO,只有设置/获取 属性的方法
  • Client(客户端):可以发送请求或发送传输对象到业务对象
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
68
69
70
71
72
73
74
75
76
77
78
79
// step1
public class StudentVO{
private String name;
private int rollNo;
StudentVo(String name, int rollNo){
this.name = name;
this.rollNo = rollNo;
}
public String getName(){
return name;
}
public void setName(String name){
thi.name = name;
}
public int getRollNo(){
return rollNo;
}
public void setRollNo(int rollNo){
this.rollNo = rollNo;
}
}

// step2
import java.util.ArrayList;
import java.util.List;
public class StudentBO{
List<StudentVO> students;
public StudentBO(){
students = new ArrayList<StudentVO>();
studentVO student1 = new StudentVO("Robert", 0);
studentVO student2 = new StudentVO("John", 1);
student1.add(student1);
student2.add(student2);
}
public void deleteStudent(StudentVO student){
students.remove(student.getRollNo());
System.out.println("Student: Roll No "
+ student.getRollNo()
+ ", deleted from database."
);
}
public List<StudentVO> getAllStudents(){
return students;
}
public StudentVO getStudent(int rollNo){
return students.get(rollNo);
}
public void updateStudent(StudentVO student){
students.get(studentNo()).setName(student.getName());
System.out.println("Student: Roll No "
+ student.getRollNo()
+ ", updated in the database."
);
}
}

// public class TransferObjectPatternDemo{
public static void main(String[] args){
StudentBO studentBusinessObject = new StudentBO();
for(StudentVO student: studentBusinessObject.getAllStudent()){
System.out.println("Student: [Roll No: "
+ student.getRollNo()
+ ", Name: "
+ student.getName()
+ "]"
);
}
studentVO student = studentBusinessObject.getAllStudents().get(0);
student.setName("Micheal");
studentBusinessObject.updateStudent(student);
student = studentBusinessObject.getStudent(0);
System.out.println("Student: [Roll No: "
+ student.getRollNo()
+ ", Name: "
+ student.getName()
+ "]"
);
}
}

Abstract Data Type

概述

概念

  • data:数据,对客观事物的符号表示,在计算机科学中,指 所以能输入计算机中、并被计算机处理的符号总称
  • data element:数据元素,数据基本单位,在计算机中通常 作为整体考虑
  • data item:数据项,数据元素的组成部分
  • data object:数据对象,性质相同的数据元素的集合,数据 的子集

Data Structure

数据结构:相互直接存在一种或多种特定关系的数据元素的集合

  • 使用基本类型不断组合形成的层次结构
  • 某种意义上,数据结构可以看作是一组具有相同结构的值
  • $D$:数据元素有限集
  • $S$:结构/关系有限集,数据元素之间相互之间的逻辑关系
  • 集合:结构中数据元素只有同属一个集合的关系

  • 线性结构:结构中数据元素之间存在一对一关系

    • 存在唯一一个被称为“第一个”的数据元素
    • 存在唯一一个被称为“最后一个”的数据元素
    • 除第一个外,每个数据元素均只有一个前驱
    • 除最后一个外,每个数据元素均只有一个后继
  • 树型结构:结构中数据元素存在一对多关系

  • 图状/网状结构:结构中数据元素存在多对多的关系

关系表示

  • 逻辑结构:数据结构中描述的逻辑关系
  • 物理/存储结构:数据结构在计算机中的表示(映像)

数据元素之间关系在计算机中映像/表示方法/存储结构

  • 顺序映像:借助元素在存储器中的相对位置表示数据元素 之间的逻辑关系

    • 需要使用地址连续的存储单元依次存储数据元素
    • 所以需要静态分配空间,即给可能数据对象预分配空间 ,空间不够时只能重新分配
    • 由此得到顺序存储结构
  • 非顺序/链式映像:借助指示元素存储地址的指针表示数据 元素之间的逻辑关系

    • 可以使用任意存储单元存储数据元素,另外存储一个指示 其直接后继的信息
    • 常用动态分配空间,即在需要创建数据对象时给其分配空间
    • 也可以静态分配空间,使用地址连续的存储单元存储数据 对象,此时指示元素可以是序号
    • 由此得到链式存储结构
  • node:节点,数据元素信息、直接后继元素信息的存储映像
  • 数据域:存储元素信息的域
  • 指针域:存储直接后继位置的域

Data Type

数据类型:值的集合和定义在值集上的一组操作的总称

  • atomic data type:原子类型,值不可分解
  • fixed-aggregate data type:固定聚合类型,值由确定数目 的成分按某种结构组成
  • variable-aggregate data type:可变聚合类型,值的成分 数目不确定
  • 结构类型:固定、可变聚合类型的统称,值由若干成分按某种 结构组成,可分解,其成分可以是结构或非结构
    • 结构类型可以看作是由一种数据结构和定义在其上的一组 操作组成
  • 在计算机中,每个处理核心(硬件、软件)都提供了一组原子 类型或结构类型
  • 从硬件角度,引入数据类型是作为解释计算机内存中信息 含义的手段
  • 对使用者而言,实现了信息的隐蔽

Abtract Data Type

ADT:抽象数据类型,一个数学模型已经定义在该模型上的 一组操作

  • $D$:数据对象
  • $S$:D上的关系集
  • $P$:对D的基本操作集
  • 根据其行为而不是其内部表示定义类型
  • 实质上与数据类型是同一个概念,“抽象”的意义在于数据类型的 数学抽象特性
    • 或者说抽象数据类型范畴更广,包括自定义数据类型
  • 抽象层次越高,含有该抽象数据类型的程序复用程度越高

面向对象

  • ADT是面向对象编程的核心

  • 将对象行为和其实现相分离,这也是面向对象编程的基本 技术

    • simplicity:简单性,隐藏细节方便理解
    • flexibility:灵活性,类通过对外行为被定义,可以 自由改变内部实现
    • security:安全性,扮演防火墙,确保实现、用户彼此 分离

Python安装配置

Python

Python3包管理安装

  • CentOS7依赖缺失
    • zlib-devel
    • bzip2-devel
    • readline-devel
    • openssl-devel
    • sqlite(3)-devel
  • 根据包用途、名称可以确认对应的应用,缺少就是相应 -devel(-dev)

Python Implementation

名称中flag体现该发行版中python特性

  • -d:with pydebug

  • -m:with pymalloc

  • -u:with wide unicode

  • pymalloc是specialized object allocator
    • 比系统自带的allocator快,且对python项目典型内存 分配模式有更少的开销
    • 使用c的malloc函数获得更大的内存池
  • 原文:Pymalloc, a specialized object allocator written by Vladimir Marangozov, was a feature added to Python2.1. Pymalloc is intended to be faster than the system malloc() and to have less memory overhead for allocation patterns typical of Python programs. The allocator uses C’s malloc() function to get large pools of memory and then fulfills smaller memory requests from these pools.J

    注意:有时也有可能只是hard link

Python配置

Python相关环境变量

  • PYTHONPATH:python库查找路径
  • PYTHONSTARTUP:python自动执行脚本

自动补全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import readline
import rlcompleter
# 为自动补全`rlcompleter`不能省略
import atexit
readline.parse_and_bind("tab:complete")
# 绑定`<tab>`为自动补全

try:
readline.read_history("/path/to/python_history")
# 读取上次存储的python历史
except:
pass
atexit.register(
readline.write_history_file,
"/path/to/python_history"
)
# 将函数注册为推出python环境时执行
# 将python历史输入存储在的自定以文件中
# 这部分存储、读取历史其实不必要

del readline, rlcompleter
  • 每次在python解释器中执行生效

  • 保存为文件python_startup.py,将添加到环境变量 PYTHONSTARTUP中,每次开启python自动执行

    1
    2
    3
    # .bashrc
    export PYTHONSTARTUP=pythonstartup.py
    # 这个不能像*PATH一样添加多个文件,只能由一个文件

Pip

python包、依赖管理工具

  • pip包都是源码包

    • 需要在安装时编译,因此可能在安装时因为系统原因出错
    • 现在有了wheels也可以安装二进制包

安装

  • 编译安装python一般包括pipsetuptools
  • 系统自带python无pip时,可用aptyum等工具可以直接安装
  • 虚拟python环境,无般法使用系统包管理工具安装pip,则只能 下载pip包使用setuptools安装

配置

配置文件:~/.config/pip/pip.conf

1
2
3
4
5
[global]
index-url = https:?//pypi.tuna.tsinghua.edu.cn/simple/
# pypi源地址
format = columns
# pip list输出格式(legacy,columns)

依赖管理

pip通过纯文本文件(一般命名为requirements.txt)来记录、 管理python项目依赖

  • $ pip freeze:按照package_name=version的格式输出 已安装包 $ pip install -r:可按照指定文件(默认requirements.txt) 安装依赖

Virtualenv/Venv

虚拟python环境管理器,使用pip直接安装

  • 将多个项目的python依赖隔离开,避免多个项目的包依赖、 python版本冲突
  • 包依赖可以安装在项目处,避免需要全局安装python包的权限 要求、影响

实现原理

$ virtualenv venv-dir复制python至创建虚拟环境的文件夹中, $ source venv-dir/bin/activate即激活虚拟环境,修改系统环境 变量,把python、相关的python包指向当前虚拟环境夹

Virtualenv使用

Pyenv

python版本管理器,包括各种python发行版

安装

不需要事先安装python

  • 从github获取pyenv:git://github.com/yyuu/pyenv.git

  • 将以下配置写入用户配置文件(建议是.bashrc),也可以在 shell里面直接执行以暂时使用

    1
    2
    3
    export PYENV_ROOT="$HOME/pyenv路径"
    export PATH="$PYENV_ROOT/bin:$PATH"
    eval "$(pyenv init -)"

    以上配置可参见home_config/bashrc_addon,以项目详情为准

Pyenv安装Python发行版问题

使用pyenv安装python时一般是从PYTHON_BUILD_MIRROR_URL表示 的地址下载安装文件(或者说整个系统都是从这个地址下载),缺省 是http://pypi.python.org,但国内很慢

#todo
  • 设置这个环境变量为国内的镜像站,如 http://mirrors.sohu.com/python,但这个好像没用
  • 在镜像站点下载安装包,放在pyenv/cache文件夹下(没有就 新建)

pyenv安装python时和使用一般安装应用一样,只是安装prefix不是 /usr/bin/之类的地方,而是pyenv安装目录,因此pyenv编译安装 python也需要先安装依赖

实现原理

修改$PATH环境变量

  • 用户配置文件将PYENV_ROOT/bin放至$PATH首位
  • 初始化pyenv时会将PYENV_ROOT/shims放至$PATH首位

shimsbin放在最前,优先使用pyenv中安装的命令

  • bin中包含的是pyenv自身命令(还有其他可执行文件,但是 无法直接执行?)

  • shims则包含的是所有已经安装python组件

    • 包括python、可以执行python包、无法直接执行的python包

    • 这些组件是内容相同的脚本文件,仅名称是pyenv所有安装 的python包

      • 用于截取python相关的命令
      • 并根据设置python发行版做出相应的反应
      • 因此命令行调用安装过的python包,pyenv会给提示 即使不是安装在当前python环境中

因此一般将命令放在.profile文件中,这样每次登陆都会设置好 pyenv放在.bashrc中会设置两次(没有太大关系)

使用指定Python发行版

  • $ pyenv local py-version指定是在文件夹下生成 .python-version文件,写明python版本

  • 所有的python相关的命令都被shims中的脚本文件截取

pyenv应该是逐层向上查找.python-version文件,查找到文件则 按照相应的python发行版进行执行,否则按global版本

Conda

通用包管理器

  • 管理任何语言、类型的软件

    • conda默认可以从http://repo.continuum.io安装已经 编译好二进制包

    • conda包和pip包只是部分重合,有些已安装conda包 甚至无法被pip侦测到(非python脚本包)

    • python本身也作为conda包被管理

  • 创建、管理虚拟python环境(包括python版本)

安装

  • conda在Miniconda,Anaconda发行版中默认安装

    • Miniconda是只包括conda的发行版,没有Anaconda中默认 包含的包丰富

    • 在其他的发行版中可以直接使用pip安装,但是这样安装的 conda功能不全,可能无法管理包

  • Miniconda、Anaconda安装可以自行设置安装位置,无需介怀

配置

conda配置文件为$HOME/.condarc,其中可以设置包括源在内 配置

1
2
3
4
5
channels:
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
- defaults
show_channel_urls: true

添加国内源

conda源和pypi源不同(以下为清华源配置,当然可以直接修改 配置文件)

1
2
3
$ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
$ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
$ conda config --set show_channel_urls yes
  • conda源不是pypi源,不能混用

Win平台设置

  • 添加菜单项

    1
    2
    3
     # 可用于恢复菜单项
    $ cd /path/to/conda_root
    $ python .\Lib\_nsis.py mkmenus
  • VSCode是通过查找、执行activate.bat激活虚拟环境

    • 所以若虚拟环境中未安装conda(无activate.bat) 则虚拟环境无法自动激活

常用命令

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
$ conda create [--clone ori_env] -n env [packages[packages]]
# 创建虚拟环境
# python也是conda包,可指定`python=x.x`
$ conda remove -n env --all
# 删除虚拟环境

$ conda info -e
$ conda env list
# 列出虚拟环境
$ conda info
# 列出conda配置

$ conda activate env
# 激活env环境
$ conda deactivate
# 退出当前环境

$ conda list -n env
# 列出env环境/当前环境安装conda包
$ conda search package
# 搜索包
$ conda install [-n env] packages
# env环境/当前环境安装conda包
$ conda update [-n env] packages
# env环境/当前环境升级conda包
$ conda remove [-n env] packages
# env环境/当前环境移除包
  • 使用conda而不是pip安装包更合适,方便管理
  • 创建新环境时,默认不安装任何包,包括pip,此时切换到 虚拟环境后,pip等命令都是默认环境的命令

Pipenv

pip、virtualenv、Pipfile(版本控制)功能的综合,事实上就 依赖于pip、virtualenv(功能封装)

  • $ pipenv sync/install替代$ pip install
  • $ pipenv shell替代$ activate
  • $ pipenv run甚至可以不用激活虚拟环境运行某些命令
  • Pipfile控制dev、release包管理,Pipfile.lock锁定包依赖

安装

  • 使用pip直接安装
  • 系统安装源里有pipenv,也可以用系统包管理工具安装

实现原理

Python版本

pipenv和virtualenv一样指定python版本也需要已经安装该python 版本

  • $PATH中的路径无法寻找到相应的python版本就需要手动 指定

  • 不是有版本转换,将当前已安装版本通过类似2to3的“中 间层”转换为目标版本

虚拟环境

pipenv会在~/.local/share/virtualenv文件夹下为所有的虚拟 python环境生成文件夹

  • 文件夹名字应该是“虚拟环境文件夹名称-文件夹全路径hash”

  • 包括已安装的python包和python解释器

  • 结构和virtualenv的内容类似,但virtualenv是放在项目目录下

  • $ python shell启动虚拟环境就是以上文件夹路径放在 $PATH最前

依赖管理

pipenv通过Pipfile管理依赖(环境)

  • 默认安装:$ pipenv install pkg

    • 作为默认包依赖安装pkg,并记录于Pipfile文件 [packages]条目下

    • 相应的$ pipenv install则会根据Pipfile文件 [packages]条目安装默认环境包依赖

  • 开发环境安装:$ pipenv install --dev pkg

    • 作为开发环境包依赖安装pkg,并记录于Pipfile 文件[dev-packages]条目下

    • 相应的$ pipenv intall --dev则会根据Pipfile 文件中[dev-packages]安装开发环境包依赖

Pipfile和Pipfile.lock

  • Pipfile中是包依赖可用(install时用户指定)版本
  • Pipfile.lock则是包依赖具体版本
    • 是pipenv安装包依赖时具体安装的版本,由安装时包源的 决定
    • Pipfile.lock中甚至还有存储包的hash值保证版本一致
    • Pipfile是用户要求,Pipfile.lock是实际情况

因此

  • $ pipenv install/sync优先依照Pipfile.lock安装具体 版本包,即使有更新版本的包也满足Pipfile的要求

  • PipfilePipfile.lock是同时更新、内容“相同”, 而不是手动锁定且手动更新Pipfile,再安装包时会默认更新 Pipfile.lock

Pipenv用法

详情https://docs.pipenv.org

创建新环境

具体查看$pipenv --help,只是记住$pipenv --site-packages 表示虚拟环境可以共享系统python包

默认环境和开发环境切换

pipenv没有像git那样的切换功能

  • 默认环境“切换”为dev环境:$ pipenv install --dev
  • dev环境“切换”为默认环境:$ pipenv uninstall --all-dev

同步

$ pipenv sync

官方是说从Pipfile.lock读取包依赖安装,但是手动修改Pipfile$ pipenv sync也会先更新Pipfile.lock,然后安装包依赖, 感觉上和$ pipenv install差不多

Pipenv特性

和Pyenv的配合

pipenv可以找到pyenv已安装的python发行版,且不是通过$PATHshims获得实际路径

  • pipenv能够找到pyenv实际安装python发行版的路径versions, 而不是脚本目录shims

  • pipenv能自行找到pyenv安装的python发行版,即使其当时没有 被设置为local或global

    • pyenv已安装Anaconda3和3.6并指定local为3.6的情况下 $ pipenv --three生成的虚拟python使用Anaconda3

    • 后系统全局安装python34,无local下pipenv --three 仍然是使用Aanconda3

    • 后注释pyenv的初始化命令重新登陆,pipenv --three就 使用python34

目前还是无法确定pipenv如何选择python解释器,但是根据以上测试 和github上的feature介绍可以确定的是和pyenv命令有关

pipenv和pyenv一起使用可以出现一些很蠢的用法,比如:pyenv指定 的local发行版中安装pipenv,然后用这pipenv可以将目录设置为 另外版本虚拟python环境(已经系统安装或者是pyenv安装)

总结

除了以上的包管理、配置工具,系统包管理工具也可以看作是python 的包管理工具

  • 事实上conda就可以看作是pip和系统包管理工具的交集
  • 系统python初始没有pip一般通过系统包管理工具安装

使用场景

优先级:pip > conda > 系统包管理工具

  • 纯python库优先使用pip安装,需要额外编译的库使用conda
  • conda源和系统源都有的二进制包,优先conda,版本比较新

2018/04/06经验

最后最合适的多版本管理是安装pipenv

  • 系统一般自带python2.7,所以用系统包管理工具安装一个 python3

  • 使用新安装的python3安装pipenv,因为系统自带的python2.7 安装的很多包版过低

  • 最后如果对python版本要求非常严格

    • 还可以再使用pyenv安装其他版本
    • 然后仅手动启用pyenv用于指示pipenv使用目标python版本

2019/02/20经验

直接全局(如/opt/miniconda)安装Miniconda也是很好的选择

  • 由conda管理虚拟环境,虚拟环境创建在用户目录下,登陆时 激活

Python注意事项

Python原生数据结构

list

  • 方法

    • ==list== 是逐值比较
    • __contains__:方法中使用 == 比较元素
      • in 判断列表包含时也是逐值比较
  • 迭代技巧

    • 需要修改列表元素时尽量不直接迭代列表,考虑
      • 新建列表存储元素值
      • 迭代列表下标
    • 迭代过程会更改列表元素数量时
      • 使用 .pop 方法
      • 确定迭代数量
  • 运算注意

    • .append:直接修改原列表,不返回
    • .extend:直接修改原列表,不返回
    • __add__:返回新列表

参数

  • 勿使用列表、字典等指针类型作为默认参数,否则函数重入结果很可能出现问题
    • 原因:函数体中任何对参数的修改都会被保留
    • 替代方式:None + 函数体内判断

迭代器

  • 需要多次迭代时,应该将迭代器转换为可重复迭代数据结构,如:列表
    • 迭代器值会被消耗

Rust 程序设计笔记

参数、环境变量

  • std::env::args():返回所有参数的一个迭代器,第一参数 是可执行文件,包含任何无效unicode字符将panic!

    args: Vec = std::env::args().collect()

  • std::env::var("env_var"):获取设置的环境变量值,返回 一个Result

    • 环境变量设置时返回包含其的Ok成员
    • 未设置时返回Err成员

    设置“环境变量”并执行程序:$>ENV_NAME=val cargo run

马恩的社会主义观

  • 生产资料:社会所有制、公有制
  • 经济运行方式:消灭商品货币关系,实行有组织的计划生产
  • 分配方式:实行按劳分配
  • 阶级与国家:消灭了阶级和阶级差别,消灭了作为阶级统治工具的国家

对于斯大林和现实中国的社会主义观与马列的社会注意观之间的差异, 做出符合现实与逻辑的、一以贯之的解释

  • 既是理论对我们提出的挑战,
  • 也是社会建设的实践要求剋人 因此这个问题的回答,或者说正确解决这个矛盾,就具有重要的理论 意义和实践意义

在资本主义社会和共产主义社会之间,有一个从前者转变为后者 的个命转变时期,同这个时期相适应的也有一个政治上的过渡时期, 这个时期的国家,只能是国产阶级的革命专政

社会主义初级阶段

  • 社会性质,已经是社会主义了
  • 还处于社会主义社会初级阶段,还没有根本摆脱贫穷落后的不发达状态
  • 实际上处于马克思讲的从资本主义社会向社会主义社会过渡时期, 是过渡时期中的一个比较初级的阶段

重新定位社会主义初级阶段的意义

  • 可以消除现实与马列理论间的诸多矛盾,端正被扭曲的马克思主义 理论与社会主义形象
  • 在实践中脚踏实地,从当代中国现实国情触发,探索具有中国特色 的通向社会主义的道路,更好的沿着社会主义方向前进

邓小平

  • 社会主义初级阶段最根本任务就是发展生产力,社会主义的优越性 归根结底就是体现在他的生产力比资本主义发展得更快一些、更高 一些,
  • 改革都是为了一个目的,就是扫除发展社会生产力障碍
  • 对内搞活了经济,是活了社会主义,没有伤害社会主义的本质,因为 从政治上讲,我们的国家机器是社会主义性质得, 有能力保障 社会主义制度,从经济上讲,我国的社会主义经济在工业、农业、 商业和其他方面已经建立相当坚实的基础
  • 社会主义的目的就是要全国人民共同富裕,不是两极分化。如果我们 的政策导致了两级分化,我们就失败了;如果产生了什么新的资产 阶级,那么我们就整的走了邪路了
  • 公有制占主体,共同富裕,是我们必须坚持的社会主义根本原则

概括

  • 要发展生产力
  • 要坚持公有制和按劳分配为主体
  • 走共同富裕道路,防止出现两极分化
  • 坚持四项基本原则
  • 市场与计划都是手段,利用资本主义发展社会主义
  • 用三个有利于的标准衡量改革开放的成就

社会主义本质

解放生产力,发展生产力,消灭剥削,消除两极分化,最终达到共同富裕

  • 在高于特征的层次上,在目标和目的的层次上揭示了社会主义 的内在要求
  • 突出生产力的基础地位,是对社会主义的具有时代特征的新认识
  • 突出了社会主义的目的共同富裕
  • 在动态中描述了社会主义的本质

社会主义的根本任务:发展生产力

  • 理论上:发展生产力是马克思主义的基本原则
  • 实践上:是解决社会主义初级阶段主要矛盾的要求
  • 前途上:是巩固社会主义制度、增强社会主义吸引力, 向共产主义过渡的根本途径

生产力与社会主义关系

  • 阶级斗争和社会革命并不同工业发达程度、生产力发展水平成正比, 经验的研究似乎是,它只在大工业的发展时期的一定阶段才有可能

  • 共产主义个命将在一切文明国家里同时发生,就这些文明国家而言, 由于资本主义发展不均衡,生产力发展水平也是有区别的

  • 革命的发生、完成要区别开来,受资本主义大生产和世界市场的 存在,发生革命的先后次序,并不单纯取决于一国的生产力发展水平

  • 开始革命,走上社会主义道路,可以不经过资本主义充分发展的阶段, 但是完成革命,实现社会主义乃至共产主义,则非有生产力的巨大 发展不可

  • 人类社会发展规律不可改变,资本主义虽然不能避免,但是资本 主义制度可以避免,可以缩短,过渡阶段就是对资本主义阶段的补课 ,对经济文化落后国家,问题是如何发展、利用、节制资本主义

毛思中的社会主义理论

  • 在一个经济文化比较落后的国家,在新民主主义革命胜利后,如何 过渡到社会主义
  • 在社会主义改造完成之后,如何进行社会主义建设

准备过渡—->开始过渡

  • 已经开始执行社会主义革命的任务

    • 没收官僚资本,建立社会主义性质的国营经济
      • 只有没收了占比80%的官僚资本,才能对20%的民族资本 采取和平的方式,逐步进行社会主义改造
    • 开始引导私人资本主义经济走国家资本主义的道路
      • 赎买:为了便于过渡到社会主义,保持极大的生产 组织是很重要的
      • 民族资产阶级有两面性,是内部矛盾 为什么接受
      • 社会主义力量增长,政权在手中
      • 社会主义性质经济基本控制了国家经济命脉,在国民 经济中处于领导地位和作用
      • 制定了适宜的政策、方法 为什么愿意
      • 政治上,民族资产阶级拥护国家、宪法、共同纲领
      • 经济上,民族资产阶级原因接受国家资本主义、国家的 引导和监督,民族资本的存在对于加速国民经济的恢复 发展有重要作用
      • 历史上,不愿看到同一战线破裂
      • 可以让工人阶级学会如何管理工商业,经营、组织生产
    • 引导个体农民走上了互助合作社的道路
  • 新民主主义社会是一个具有过渡性质的社会

  • 这个革命的性质是社会主义革命

    • 革命的性质是由社会的主要矛盾决定的

过渡时期总路线

  • 主体:国家工业化
    • 主体?
    • 工业化时生产力发展水平的重要标致
    • 近代中国的两大任务:民族独立和人民解放,国家繁荣富强
  • 两翼:农业、手工业和资本主义工商业的改造
  • 工业化和社会主义改造并举的路线

工业化道路

  • 资本主义工业化道路走不通

    • 民族资本主义脆弱
    • 不满足条件
      • 有大量人身自由,但丧失了生产资料的无产者存在
      • 大量货币集中在少数人手里
  • 社会主义

    • 国营经济力量比较强大
    • 民族资本主义经济弱小,不能成为主义依靠力量
    • 对个体农业的社会主义改造,是实现国家工业化的必要条件
    • 国际环境促使中国选择了社会主义

国家资本主义

改造资本主义工商业的过渡形式,不是独立经济形态,性质随国家 性质而转移

  • 资本主义制度:资产阶级政府直接产于国家经济生活,通过国有化 或国家预算建设新的企业等办法,使国民经济某些部门、企业转移到 国家手里
  • 社会主义制度:能够加以限制、规定活动范围的资本主义,同国家 联系着

三种形式

  • 初级:国营经济与资本主义经济在企业外部发生不固定联系
  • 中极:国营经济和资本主义经济在企业外部发生固定联系
    • 以上仍然资本主义性质,追逐理论最大化
    • 但是和国营经济的密切联系,在一定程度上被纳入国家计划 轨道,限制了剥削
  • 高级:国营经济和资本主义经济在企业内部联系合作、并居于领导 地位,即公私合营经济,包括单个企业和全行业
    • 社会主义控制企业流通过程、生产过程
    • 私人资本具有资本性质,但是已经失去了独立地位

农村社会主义

农民工体经济:生产资料归农民私有,与个人劳动相结合的半自然经济 或小商品经济

  • 生产资料私有—>发展个体经济的积极性—>自发的资本主倾向
  • 以自身劳动去基础,不存在剥削—>互助合作积极性—> 被引导走向社会主义

农业合作化

  • 限制生产力法渐
  • 分散性、盲目性同国营经济、工业化目标冲突
  • 小农经济不稳定,必然导致农村不稳定

PAC(Proxy Auto-Config)


pac事实上是一个js脚本,定义如何根据浏览器器访问url不同, 自动选取适当proxy

主函数

str = function FindProxyForUrl(url, host){
}
  • 参数
    • url:浏览器访问的完整url地址,如: http://bing.com
    • host:url中的host部分bing.com
  • 返回值:字符串变量,表示应该使用何种”代理“,可以是 以下元素,或者是使用;分隔的多个组合,此时浏览器依次 尝试 (“据说”每隔30min,浏览器就会尝试之前失败的“代理元素”)

    • DIRECT:不使用代理,直接连接
    • PORXY host:post:使用指定http代理@host:post
    • SOCKS host:post:使用指定socks代理
    • SOCKS5 host:post:使用指定的socks5代理

浏览器在访问每个url时都会调用该函数

可用预定义JS函数

host、domain、url相关js函数

  • bool = isPlainHostName(host)host不包含域名返回 true

    true = isPlainHostName(“www”) false = isPlainHostName(“baidu.com”)

  • bool = dnsDomainIs(host, domain)hostdomain相 匹配返回true

    true = dnsDomain(“www.google.com”, “.google.com”) false = dnsDomain(“www.apple.com”, “.google.com”)

  • bool = localHostOrDomainIs(host,domain)hostdomain匹配、host不包含domain返回true(按照函数名 理解)

    true = localHostOrDomainIs(“www.google.com”, “www.google.com”) true = localHostOrDomainIs(“www”, “www.google.com”) false = localHostOrDomainIs(“www.apple.com”, “www.google.com”)

  • bool = isResolvable(host):成功解析host返回true

  • bool = isInNet(host, pattern, mask)host处于 pattern指定网段/地址返回truehost如果不是ip 形式,将解析成ip地址后处理;mask指定匹配部分,mask 就是和子网掩码类似

    isinnet(“192.168.3.4”, “192.168.0.0”, “255.255.0.0”) -> true isinnet(“192.168.3.4”, “192.168.0.0”, “255.255.255.255”) -> false

  • str = myipaddress():字符串形式返回本机地址

  • int = dnsdomainlevels(host):返回host中域名层级数

    0 = dnsdomainlevels(“www”) 2 = dnsdomainlevels(“www.google.com”)

  • bool = shexpmatch(str, shexp)str符合shexp 正则表达式返回true

    shexpmatch(“www.apple.com/downloads/macosx/index.html”, “/macosx/“) -> true. shexpmatch(“www.apple.com/downloads/support/index.html”, “/macosx/“) -> false.

时间相关JS函数

  • bool = weekdayrange(wd1, wd2, gmt):时间处于指定时间段 返回true

    weekdayrange(“mon”, “fri”) 星期一到星期五(当地时区)为true weekdayrange(“mon”, “fri”, “gmt”) 从格林威治标准时间星期一到星期五为true weekdayrange(“sat”) 当地时间星期六为true weekdayrange(“sat”, “gmt”) 格林威治标准时间星期六为true weekdayrange(“fri”, “mon”) 从星期五到下星期一为true(顺序很重要)

  • bool = daterange(..):时间处于指定时间段返回true (包括首尾日期)

    daterange(1) 当地时区每月第一天为true daterange(1, “gmt”) gmt时间每月的第一天为true daterange(1, 15) 当地时区每月1号到15号为true daterange(24, “dec”) 在当地时区每年12月24号为true daterange(24, “dec”, 1995) 在当地时区1995年12月24号为true daterange(“jan”, “mar”) 当地时区每年第一季度(1月到3月)为true daterange(1, “jun”, 15, “aug”) 当地时区每年6月1号到8月15号为true daterange(1, “jun”, 15, 1995, “aug”, 1995) 当地时区1995年6月1号到8月15号为true daterange(“oct”, 1995, “mar”, 1996) 当地时区1995年10月到1996年3月为true daterange(1995) 当地时区1995年为true daterange(1995, 1997) 当地时区1995年初到1997年底为true

  • bool = timerange(..):时间处于指定时间段返回true

    timerange(12)中午12点到下午1点之间为true timerange(12, 13)同上例 timerange(12, “gmt”)在gmt时间中午12点到下午1点之间为true timerange(9, 17)上午9点到下午5点之间为true timerange(8, 30, 17, 00)上午8点30分到下午5点之间为true. timerange(0, 0, 0, 0, 0, 30)午夜0点到其后的30秒内为true

Web 代理

代理

  • 正向代理:模拟client的代理
  • 反向代理:模拟server的代理,通常带有负载均衡,通常不处理 用户数据,也有可能处理静态数据(图片,静态页面等),典型 就是nginx服务器