字典:
字典的定义
~字典(存放键值对组合的容器)
~字典中的每个元素都是由两部分构成的, 冒号前面是键冒号后面是值。
~通过键获取对应的值(可空类型, 因为给的键有可能没有与之对应的值)
~key ---> value
例如:
var dict: [String: String] = ["abacus": "算盘", "abnormal": "异常的", "hello" : "你好", "good": "好的"]
print(dict["hello"]) //输出为:Optional("你好") - "Optional"意思为可空。
print(dict["hello"]!) //输出为:你好
print(dict["abcxyz"]) //输出为:nil
添加/删除/修改字典中的元素
~添加元素:
dict["shit"] = "狗屎"
dict["delicious"] = "好吃的"
print(dict)
// 输出结果为:["hello": "你好", "abacus": "算盘", "good": "好的", "shit": "狗屎", "delicious": "好吃的", "abnormal": "异常的"]
~删除元素:
dict["hello"] = nil
print(dict)
// 输出结果为:["shit": "狗屎", "abacus": "算盘", "good": "好的", "delicious": "好吃的", "abnormal": "异常的"]
print(dict["hello"])
// 输出结果为:nil
~修改元素:
dict["shit"] = "牛粪"
print(dict)
// 输出结果为:["shit": "牛粪", "abacus": "算盘", "good": "好的", "delicious": "好吃的", "abnormal": "异常的"]
遍历字典中的键/值
~遍历字典中所有的键:
for key in dict.keys {
print("\(key) ---> \(dict[key])")
}
// 输出结果为:shit ---> Optional("牛粪");abacus ---> Optional("算盘");good ---> Optional("好的");delicious ---> Optional("好吃的");abnormal ---> Optional("异常的")
~遍历字典中所有的值:
for value in dict.values {
print(value)
}
// 输出结果为:牛粪;算盘;好的;好吃的;异常的
~直接通过一个元组获得字典中的键和值(原始类型):
for (key, value) in dict {
print("\(key) ---> \(value)")
}
// 输出结果为:shit ---> 牛粪;abacus ---> 算盘;good ---> 好的;delicious ---> 好吃的;abnormal ---> 异常的
集合:
集合的定义:
// 集合中的元素不能重复
var a: Set<Int> = [1, 2, 3, 1, 2, 5]
var b: Set<Int> = [3, 5, 7, 9, 11]
print(a) // 输出结果为:[5, 2, 3, 1]
print(b) // 输出结果为:[5, 7, 3, 9, 11]
添加/删除集合中的元素:
~添加元素:
a.insert(100)
print(a) // 输出结果为:[5, 100, 2, 3, 1]
~删除元素:
a.remove(2)
print(a) // 输出结果为:[5, 100, 3, 1]
集合的交/差/并:
var a: Set<Int> = [1, 3, 2, 5]
var b: Set<Int> = [3, 5, 7, 9, 11]
~交集(a和b都有的元素):
print(a.intersect(b)) // 输出为:[5, 3]
~并集(a和b的所有元素):
print(a.union(b)) // 输出为:[2, 9, 5, 7, 3, 1, 11]
~差集(a有b没有的元素):
print(a.subtract(b)) // 输出为:[2, 1]
集合之间的关系:
let c: Set<Int> = [1, 3]
let d: Set<Int> = [2, 1000, 10000]
~判断a和b是不是相等:
print(a == b) // 输出为:false
~判断c是不是a的子集:
print(c.isSubsetOf(a)) // 输出为:true
~判断a是不是c的超集:
print(a.isSupersetOf(c)) // 输出为:true
~判断两个集合是否相交:
print(a.isDisjointWith(d)) // 输出为:false
数组的三个重要功能:
let array = [23, 37, 96, 55, 40, 92, 68, 88]
过滤:
let newArray1 = array.filter { $0 > 50 }
print(newArray1)
// newArray1 = [96, 55, 92, 68, 88]
let newArray2 = array.filter { $0 % 2 == 0 }
print(newArray2)
// newArray2 = [96, 40, 92, 68, 88]
映射:
let newArray3 = array.map { $0 * $0 }
print(newArray3)
// newArray3 = [529, 1369, 9216, 3025, 1600, 8464, 4624, 7744]
let newArray4 = array.map { sqrt(Double($0)) }
print(newArray4)
// newArray4 = [4.79583152331272, 6.08276253029822, 9.79795897113271, 7.41619848709566, 6.32455532033676, 9.59166304662544, 8.24621125123532, 9.38083151964686]
缩减:
let result1 = array.reduce(0, combine: +)
print(result1)
// 输出为:499
let result2 = array.reduce(1, combine: *)
print(result2)
// 输出为:98947058073600
let result3 = array.reduce(array[0]) {
$1 > $0 ? $1 : $0
}
print(result3)
// 输出为:96
let strArray = ["I", "love", "you"]
let result4 = strArray.reduce("") { $0 + " " + $1 }
print(result4)
// 输出为: I love you
函数:
函数的定义:
~在Swift中函数是一种类型,这也就意味着函数可以作为变量或常量的类型,同理函数也可以作为另一个函数的参数或返回值
~如果函数的返回类型不是Void 那么函数中一定有return语句
~定义函数:
func 函数名(参数列表) -> 返回类型 { 函数的执行体 }
func sayHello(personName: String, alreadyGreeted: Bool = false) -> String {
if alreadyGreeted {
return "怎么又是你, " + personName + "!"
}
else {
return "你好, " + personName + "!"
}
}
函数的参数名:
~函数名(外部参数名 内部参数名: 类型, 外部参数名 内部参数名: 类型)
~如果不写外部参数名那么内部参数名也是外部参数名
~可以使用_来作为外部参数名表示省略外部参数名
func myMin(a x: Int, b y: Int) -> Int {
return x < y ? x : y
}
参数列表:
~Swift中函数的参数列表可以是可变参数列表(参数的个数是任意多个)
func sum(nums: Int...) -> Int {
var total = 0
for num in nums {
total += num
}
return total
}
print(sum()) // 输出为: 0
print(sum(999)) // 输出为:999
print(sum(1, 2, 3)) // 输出为:6
print(sum(90, 82, 37, 68, 55, 11, 99)) // 输出为:442
inout:
~inout - 输入输出参数(不仅将数据传入函数还要从函数中取出数据)
~inout类型的参数前要加上&符号
func swap(inout a: Int, inout _ b: Int) -> Void {
(a, b) = (b, a)
// let temp = a
// a = b
// b = temp
}
var a = 300, b = 500
swap(&a, &b)
print("a = \(a)") // 输出为:a = 500
print("b = \(b)") // 输出为:b = 300
func createX(inout x: Int) {
x = 1000
}
var x = 1
createX(&x)
print(x) // 输出为: 1000
调用函数:
~调用Swift的函数时, 在默认情况下从第二个参数开始需要写参数名
print(sayHello("王大锤", alreadyGreeted: true))
// 输出为:怎么又是你, 王大锤!
~如果没有给第二个参数赋值那么就直接使用默认值false
let str = sayHello("Jack")
print(str)
// 输出为:你好, Jack!
~调用函数的时候要写函数的外部参数名
print(myMin(a: 3, b: 5))
~调用foo函数:
func sum(a: Int, _ b: Int) -> Int {
return a + b
}
func mul(a: Int, _ b: Int) -> Int {
return a * b
}
func foo(array: [Int], fn: (Int, Int) -> Int) -> Int {
var sum = array[0]
for x in array[1..<array.count] {
sum = fn(sum, x)
}
return sum
}
let a = [1, 2, 3, 4, 5]
~当调用foo函数时第二个参数可以传什么?
~1. 所有自定义的(Int, Int) -> Int类型的函数
print(foo(a, fn: sum)) // 输出为 15
~2. 传入已有的二元运算符: +-*/%(因为运算符也是函数)
print(foo(a, fn: +)) // 输出为 15
~闭包/尾随闭包:
~传入匿名函数(闭包)
~完整的闭包写法:
print(foo(a, fn: { (a, b) -> Int in
return a + b
})) // 输出为 15
~省略掉类型和不必要的括号:
print(foo(a, fn: { a, b in a + b })) // 输出为 15
~省略参数名:
print(foo(a, fn: { $0 + $1 })) // 输出为 15
~尾随闭包:
print(foo(a) { (a, b) -> Int in
return a + b
}) // 输出为 15
print(foo(a) { $0 + $1 }) // 输出为 15
~注:$0 和 $1 代表的就是两个参数!!!!!!!!!!
练习-设计函数:
~设计一个函数根据系统时间返回不同的问候语
func sayHello(name: String) -> String {
let date = NSDate()
let cal = NSCalendar.currentCalendar()
let hour = cal.component(.Hour, fromDate: date)
var greeting: String
switch hour {
case 0...6: // 不同的分支可以有重叠的部分
greeting = "怎么还没睡呀"
// fallthrough // 继续执行下一个case
case 4...10: // 匹配了一个分支之后不再匹配其他的分支
greeting = "早起的鸟儿有虫吃"
case 11...13:
greeting = "中午好"
case 14...18:
greeting = "下午好"
default:
greeting = "晚上好"
}
return name + ", " + greeting + "!"
}
print(sayHello("帅哥美女"))
~设计一个函数传入两个正整数m和n, 计算从m加到n的和
func sum(m: Int, _ n: Int) -> Int {
let (a, b) = m > n ? (n, m) : (m, n)
var value = 0
for i in a...b {
value += i
}
return value
}
print(sum(1, 100))
print(sum(5, -4))
print(sum(-1, -5))
类:
创建一个新类的步骤:
~发现类:
在对问题的描述中找名词和动词
名词会成为类或者类中的属性 动词会成为类中的方法
~步骤1: 定义类
如果你要用的类苹果已经提供了就直接进入第2步
定义类就可以创建出新的类型
①数据抽象(属性)
变量定义到类的外面就叫变量 - variable
变量定义到类的里面就叫属性 - property
数据抽象 - (找名词)
1>存储属性(保存数据的属性)
2>计算属性(通过对存储属性做运算得到的属性)
通常获得某个计算出的值的方法都可以设计成计算属性
3>只读属性(只有get{}没有set{}),例如圆的周长
②行为抽象(方法)
函数写到类的外面就叫函数 - function
函数写到类的里面就叫方法 - method
行为抽象 - (找动词)
③初始化方法/构造器
1>便利初始化方法 / 便利构造器 (调用了其他的初始化方法的初始化方法)
2>指派初始化方法 / 指派构造器(被其他初始化方法调用的初始化方法)
~步骤2: 创建对象(调用初始化方法)
~步骤3: 给对象发消息(通过给对象发消息来解决问题)
~~示例:创建一个学生类
// 步骤1: 定义类(如果你要用的类苹果已经提供了就直接进入第2步)
// 定义类就可以创建出新的类型
// 学生类
class Student {
// 变量定义到类的外面就叫变量 - variable
// 变量定义到类的里面就叫属性 - property
// 数据抽象 - 找到和学生相关的属性(找名词)
var name: String
var age: Int
// 初始化方法(构造方法/构造器) - constructor
init(name: String, age: Int) {
self.name = name
self.age = age
}
// 函数写到类的外面就叫函数 - function
// 函数写到类的里面就叫方法 - method
// 行为抽象 - 找到和学生相关的方法(找动词)
func eat() {
print("\(name)正在吃饭.")
}
func study(courseName: String) {
print("\(name)正在学习\(courseName).")
}
func watchJapaneseAV() {
if age >= 18 {
print("\(name)正在观看岛国爱情动作片.")
}
else {
print("亲爱的\(name), 我们推荐你观看《熊出没》")
}
}
}
// 步骤2: 创建对象(调用初始化方法)
let stu1 = Student(name: "隔壁老王", age: 35)
// 步骤3: 给对象发消息(通过给对象发消息来解决问题)
stu1.eat()
stu1.study("Swift程序设计")
stu1.watchJapaneseAV()
let stu2 = Student(name: "王大锤", age: 15)
stu2.eat()
stu2.study("中国近代史")
stu2.watchJapaneseAV()
类扩展(extension):
~如果在某个特定的应用场景中你发现现有的类缺少了某项功能
那么可以通过类扩展(extension)的方式现场添加这项功能
访问修饰符:
~public (公开)
~internal (内部的) - 默认
~private (私有)
①存储属性通常是private的 因为数据要保护起来
②方法一般是public的 因为方法是对象接受的消息
③如果自定义的类没有打算在其他项目中使用 可以不写访问修饰符,直接使用默认的internal修饰符表示在本项目中公开对其他项目私有
运算符重载(为自定义的类型定义运算符)
~示例:
class Fraction {
private var _num: Int
private var _den: Int
var info: String {
get {
return _num == 0 || _den == 1 ? "\(_num)" : "\(_num)/\(_den)"
}
}
init(num: Int, den: Int) {
_num = num
_den = den
simplify()
normalize()
}
func add(other: Fraction) -> Fraction {
return Fraction(num: _num * other._den + other._num * _den, den: _den * other._den)
}
func sub(other: Fraction) -> Fraction {
return Fraction(num: _num * other._den - other._num * _den, den: _den * other._den)
}
func mul(other: Fraction) -> Fraction {
return Fraction(num: _num * other._num, den: _den * other._den)
}
func div(other: Fraction) -> Fraction {
return Fraction(num: _num * other._den, den: _den * other._num)
}
func normalize() -> Fraction {
if _den < 0 {
_num = -_num
_den = -_den
}
return self
}
func simplify() -> Fraction {
if _num == 0 {
_den = 1
}
else {
let x = abs(_num)
let y = abs(_den)
let g = gcd(x, y)
_num /= g
_den /= g
}
return self
}
}
// 运算符重载(为自定义的类型定义运算符)
func +(one: Fraction, two: Fraction) -> Fraction {
return one.add(two)
}
func -(one: Fraction, two: Fraction) -> Fraction {
return one.sub(two)
}
func *(one: Fraction, two: Fraction) -> Fraction {
return one.mul(two)
}
func /(one: Fraction, two: Fraction) -> Fraction {
return one.div(two)
}
枚举:
~枚举是定义符号常量的最佳方式
~符号常量总是优于字面常量
~示例(枚举和文档注释的应用):
/**
花色的枚举
- Spade: 黑桃
- Heart: 红心
- Club: 草花
- Diamond: 方块
*/
enum Suite: String {
case Spade = "♠️"
case Heart = "❤️"
case Club = "♣️"
case Diamond = "♦️"
}
/// 一张牌
class Card {
var suite: Suite
var face: Int
/**
初始化方法
- parameter suite: 花色
- parameter face: 点数
*/
init(suite: Suite, face: Int) {
self.suite = suite
self.face = face
}
/// 牌的信息
var info: String {
get {
var str = suite.rawValue
switch face {
case 1: str += "A"
case 11: str += "J"
case 12: str += "Q"
case 13: str += "K"
default: str += "\(face)"
}
return str
}
}
}
面向对象:
继承
~从已有的类创建新类的过程称为继承
~提供继承信息的称为父类(超类/基类)
~得到继承信息的称为子类(派生类/衍生类)
~通常子类除了得到父类的继承信息还会增加一些自己特有的东西,所以子类的能力一定比父类更强大
~继承的意义在于子类可以复用父类的代码并且增强系统现有的功能
~可以将子类型的对象赋值给父类型的变量(因为子类跟父类之间是IS-A关系)
as运算符
~如果要将父类型的变量转换成子类型需要用as运算符进行类型转换
~如果能够确认父类型的变量中就是某种子类型的对象可以用as!进行转换
~如果不确定父类型的变量中是哪种子类型可以用as?尝试转换
~示例:
父类
import Foundation
enum Gender {
case Male
case Female
}
class Person {
var name: String
var age: Int
var gender: Gender
init(name: String, age: Int, gender: Gender) {
self.name = name
self.age = age
self.gender = gender
}
func eat() {
print("\(name)正在吃饭.")
}
}
子类
import Foundation
class Student: Person {
var major: String
init(name: String, age: Int, gender: Gender, major: String) {
self.major = major
super.init(name: name, age: age, gender: gender)
}
func study(courseName: String) {
print("\(name)是\(major)专业的学生.")
print("\(gender == .Male ? "他" : "她")正在学习\(courseName).")
}
}
import Foundation
class Teacher: Person {
var title: String
init(name: String, age: Int, gender: Gender, title: String) {
self.title = title
super.init(name: name, age: age, gender: gender)
}
func teach(courseName: String) {
print("\(name)\(title)正在教\(courseName).")
}
}
调用
import Foundation
let p1 = Person(name: "王大锤", age: 25, gender: .Male)
p1.eat() // 运行结果为:王大锤正在吃饭.
// 学生是人, 老师是人, 所以学生和老师的对象可以赋值给人类型的变量
let p2: Person = Student(name: "张尼玛", age: 18, gender: .Female, major: "计算机科学与技术")
p2.eat() // 运行结果为:张尼玛正在吃饭. ; 张尼玛是计算机科学与技术专业的学生.
(p2 as! Student).study("Swift程序设计")
if let temp = p2 as? Teacher {
temp.teach("Java")
}
else {
print("\(p2.name)不是老师!!!")
}
let p3: Person = Teacher(name: "老王", age: 35, gender: .Male, title: "叫兽")
p3.eat()
// 运行结果为:她正在学习Swift程序设计. ; 张尼玛不是老师!!! ; 老王正在吃饭.
重写
~父类有的方法子类可以重新实现 这个过程叫方法重写
~需要在方法前添加override关键字
~重写有时也被称为置换/覆盖/覆写
多态
~同样的对象类型接收相同的消息(调用相同的方法),但是做了不同的事情 这就是多态(polymorphism)
~实现多态的关键步骤:
①方法重写(子类在继承父类的过程中对父类已有的方法进行重写, 而且不同的子类给出各自不同的实现版本)
②对象造型(将子类对象当成父类型来使用)
~可以通过if+as?将父类型安全的转换成子类型然后再调用子类特有方法
~示例
import Foundation
class Person {
var name: String
var pet: Pet? // Person和Pet之间是HAS-A(关联)
init(name: String) {
self.name = name
}
func adopt(pet: Pet) {
print("\(name)领养了\(pet.nickname).")
self.pet = pet
}
func strike() {
if let pet = pet {
print("\(name)正在殴打\(pet.nickname).")
pet.shout()
}
else {
print("\(name)正在自虐.")
}
}
func play() {
if let pet = pet {
print("\(name)正在和\(pet.nickname)玩耍.")
pet.play()
}
else {
print("\(name)正在自嗨!")
}
}
}
Pet类(父类)
import Foundation
enum Gender {
case Male
case Female
}
class Pet {
var nickname: String
var gender: Gender
var age: Int
init(nickname: String, gender: Gender, age: Int) {
self.nickname = nickname
self.gender = gender
self.age = age
}
func eat() {
print("\(nickname)正在吃东西.")
}
func play() {
print("\(nickname)正在玩耍.")
}
func shout() {
print("\(nickname)发出了叫声.")
}
}
Cat/Dog类(Pet类的子类)
import Foundation
// Cat和Pet之间是IS-A关系(继承)
class Cat: Pet {
var hairColor: String?
override func play() {
super.play()
print("\(nickname)正在玩毛线球.")
}
override func shout() {
print("\(nickname): 喵喵喵……")
}
func catchTheMouse() {
print("\(nickname)正在抓老鼠.")
}
}
import Foundation
class Dog: Pet {
var isLarge: Bool
init(nickname: String, gender: Gender, age: Int, isLarge: Bool) {
self.isLarge = isLarge
super.init(nickname: nickname, gender: gender, age: age)
}
override func play() {
super.play()
print("\(nickname)正在接飞碟.")
}
override func shout() {
print("\(nickname): 旺旺旺……")
}
func keepTheDoor() {
if isLarge {
print("\(nickname)正在看门.")
}
else {
print("\(nickname)太小了谁也打不过.")
}
}
}
调用
import Foundation
let person = Person(name: "王大锤")
let dog = Dog(nickname: "小黄", gender: .Male, age: 1, isLarge: true)
let cat = Cat(nickname: "小咪", gender: .Female, age: 1)
person.adopt(cat)
person.strike()
let petsArray = [
Cat(nickname: "加菲", gender: .Female, age: 2),
Dog(nickname: "旺财", gender: .Male, age: 3, isLarge: true),
Dog(nickname: "大黄", gender: .Male, age: 1, isLarge: false)
for pet in petsArray {
pet.eat()
pet.play()
// 同样的对象类型(Pet类型)接收相同的消息(调用相同的方法)
// 但是做了不同的事情 这就是多态(polymorphism)
// 实现多态的关键步骤:
// 1. 方法重写(子类在继承父类的过程中对父类已有的方法进行重写, 而且不同的子类给出各自不同的实现版本)
// 2. 对象造型(将子类对象当成父类型来使用)
pet.shout()
// 可以通过if+as?将父类型安全的转换成子类型然后再调用子类特有方法
if let dog = pet as? Dog {
dog.keepTheDoor()
}
else if let cat = pet as? Cat {
cat.catchTheMouse()
}
}
面向对象七原则:
1.单一职责原则(Single Responsibility Principle)
每一个类应该专注于做一件事情。
2.里氏替换原则(Liskov Substitution Principle)
超类存在的地方,子类是可以替换的。
3.依赖倒置原则(Dependence Inversion Principle)
实现尽量依赖抽象,不依赖具体实现。
4.接口隔离原则(Interface Segregation Principle)
应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口。
5.迪米特法则(Law Of Demeter)
又叫最少知识原则,一个软件实体应当尽可能少的与其他实体发生相互作用。
6.开闭原则(Open Close Principle)
面向扩展开放,面向修改关闭。
7.组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)
尽量使用合成/聚合达到复用,尽量少用继承。原则: 一个类中有另一个类的对象。
细则
单一职责原则(Single Responsibility Principle)
因为:
可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。
所以:
从大局上看Android中的Paint和Canvas等类都遵守单一职责原则,Paint和Canvas各司其职。
里氏替换原则(Liskov Substitution Principle)
因为:
里氏替换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。里氏替换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
所以:
使用里氏替换原则时需要注意,子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。
从大局看Java的多态就属于这个原则。
依赖倒置原则(Dependence Inversion Principle)
因为:
具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类;而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口;这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造成循环依赖。
所以:
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性。
从大局看Java的多态就属于这个原则。
接口隔离原则(Interface Segregation Principle)
因为:
提供尽可能小的单独接口,而不要提供大的总接口。暴露行为让后面的实现类知道的越少越好。譬如类ProgramMonkey通过接口CodeInterface依赖类CodeC,类ProgramMaster通过接口CodeInterface依赖类CodeAndroid,如果接口CodeInterface对于类ProgramMonkey和类CodeC来说不是最小接口,则类CodeC和类CodeAndroid必须去实现他们不需要的方法。将臃肿的接口CodeInterface拆分为独立的几个接口,类ProgramMonkey和类ProgramMaster分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
所以:
建立单一接口,不要建立庞大的接口,尽量细化接口,接口中的方法尽量少。也就是要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的约定,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
从大局来说Java的接口可以实现多继承就是接口隔离原则的基础保障。
迪米特法则(Law Of Demeter)
因为:
类与类之间的关系越密切,耦合度也就越来越大,只有尽量降低类与类之间的耦合才符合设计模式;对于被依赖的类来说,无论逻辑多复杂都要尽量封装在类的内部;每个对象都会与其他对象有耦合关系,我们称出现成员变量、方法参数、方法返回值中的类为直接的耦合依赖,而出现在局部变量中的类则不是直接耦合依赖,也就是说,不是直接耦合依赖的类最好不要作为局部变量的形式出现在类的内部。
所以:
一个对象对另一个对象知道的越少越好,即一个软件实体应当尽可能少的与其他实体发生相互作用,在一个类里能少用多少其他类就少用多少,尤其是局部变量的依赖类,能省略尽量省略。同时如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一方法的话,可以通过第三者转发这个调用。
从大局来说Android App开发中的多Fragment与依赖的Activity间交互通信遵守了这一法则。
开闭原则(Open Close Principle)
因为:
开放封闭原则主要体现在对扩展开放、对修改封闭,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。软件需求总是变化的,世界上没有一个软件的是不变的,因此对软件设计人员来说,必须在不需要对原有系统进行修改的情况下,实现灵活的系统扩展。
所以:
可以通过Template Method模式和Strategy模式进行重构,实现对修改封闭,对扩展开放的设计思路。
封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,拒绝滥用抽象,只将经常变化的部分进行抽象。
组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)
因为:
其实整个设计模式就是在讲如何类与类之间的组合/聚合。在一个新的对象里面通过关联关系(包括组合关系和聚合关系)使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用其已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承。
如果为了复用,便使用继承的方式将两个不相干的类联系在一起,违反里氏代换原则,哪是生搬硬套,忽略了继承了缺点。继承复用破坏数据封装性,将基类的实现细节全部暴露给了派生类,基类的内部细节常常对派生类是透明的,白箱复用;虽然简单,但不安全,不能在程序的运行过程中随便改变;基类的实现发生了改变,派生类的实现也不得不改变;从基类继承而来的派生类是静态的,不可能在运行时间内发生改变,因此没有足够的灵活性。
所以:
组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
总结
至此,七大基本原则介绍完毕,很空洞,需要结合代码去仔细体会琢磨。