swift中的封装设计模式

提问:是否理解为什么紧密耦合是一个很大的问题?

// 产品
var products = [
    ("Kayak", "A boat for one person", 275.0, 10),
    ("Lifejacket", "Protective and fashionable", 48.95, 14),   
    ("Soccer Ball", "FIFA-approved size and weight", 19.5, 32)]
 
// 费率
func calculateTax(product:(String, String, Double, Int)) -> Double {
    return product.2 * 0.2;
}
 
// 股票的价值
func calculateStockValue(tuples:[(String, String, Double, Int)]) -> Double {
    return tuples.reduce(0, {
        (total, product) -> Double in
            return total + (product.2 * Double(product.3))
    });
}  

// 输出
print("Sales tax for Kayak: $\(calculateTax(products[0]))"); 
print("Total value of stock: $\(calculateStockValue(products))");

  • 紧密耦合的组件会让代码难以维护,比如上面的代码如果需要改变产品就会变得很麻烦,所以必须要进行更改,并测试它们的影响。在一个组件中的改变要求改变那些依赖其执行。如果在应用程序中包含大量的紧耦合,那么我们可以通过修改代码,级联和制作一个简单的修正或添加新功能的行为来避免成为大量的重写伏笔,松散耦合的组件在设计模式中的一个关键目标。
swift中的封装设计模式_第1张图片
E970A837-F775-4DB5-B73B-C0BB7C00747A.png

我删除了描述产品的价值和突出显示的语句显示所需要的功能的相应变革。在实际项目中,这些变化可以装载,以及大量更改,如果它们影响到其他紧密耦合,然后可以导致代码被修改的应用程序中很大一部分。这一级别的变化很难管理,需要进行全面的测试,以确保始终如一地应用了更改,所做的更改没有引入任何新的 bug。
了解对象模板模式
对象模板模式使用类或结构定义从中创建对象的模板。当应用程序组件需要一个对象时,它呼吁swift运行时创建通过指定模板和配置对象所需的任何运行时初始化数据值的名称。

对象模板模式:

1.第一个操作是调用组件要求swift运行时创建一个对象,提供要使用的模板和自定义将创建该对象所需的任何运行时数据值的名称。
2.第二次操作,swift运行时分配存储该对象所需的内存,并使用该模板来创建它。模板包含用于准备对象设置所使用的初始状态、 通过提供调用组件的运行时值或模板 (或两者) 中, 定义的值的初始值设定项方法和swift运行时调用初始值设定项准备使用的对象。在最后一个操作,swift运行时将它已经创建的对象给调用的组件。这三个步骤的过程,我们均可以一遍遍重复,单个模板可用于创建多个对象。
了解类结构、 对象和实例
有些的面向对象编程术语中所使用松散的日常开发但理解设计模式的时候,那可能会造成混淆。这种模式的关键条件是类、结构、对象和实例.
类和结构都是这两个模板,它们是swift如下对象模板模式的食谱。swift跟随在模板中的说明创建新对象。可以重复使用相同的模板来创建多个对象。每个对象是不同的但它创建使用相同的指令,就像一个食谱可以用于创建多个蛋糕 (添加一个 Int,一种方法来更改它的值,等等)。
Word实例具有相同的含义为对象,但它用来指用于所以创建该对象的图案的名称,产品也可以调用对象的一个实例产品班上
重要的一点是,类和结构是你写在开发过程中的说明进行操作,对象创建时应用程序。当您更改存储一个对象的值时,例如,它不会更改用于创建它的图案。

创建模板类(封装一个类)
新建一个名字叫做 Product 的类

Product.swift File 的内容

class Product {
    var name:String;
    var description:String;
    var price:Double;
    var stock:Int;
    init(name:String, description:String, price:Double, stock:Int) {
        self.name = name;
        self.description = description;
        self.price = price;
        self.stock = stock;
    }
}

上面的方法尽封装了一个简单的类,接着我会加入功能,然后再原来的文件中进行重构赋值。

// 产品通过重写的一个产品类来进行重新赋值
var products = [
    Product(name: "Kayak", description: "A boat for one person",
        price: 275, stock: 10),
    Product(name: "Lifejacket", description: "Protective and fashionable",
        price: 48.95, stock: 14),
    Product(name: "Soccer Ball", description: "FIFA-approved size and weight",
        price: 19.5, stock: 32)];

func calculateTax(product:Product) -> Double {
    return product.price * 0.2;
}
 
func calculateStockValue(productsArray:[Product]) -> Double {
    return productsArray.reduce(0, {(total, product) -> Double in
            return total + (product.price * Double(product.stock))
}); }

print("Sales tax for Kayak: $\(calculateTax(products[0]))");
print("Total value of stock: $\(calculateStockValue(products))");

PS:封装一个类,其实就是通过定义一个模板需要的属性来进行工作,这样有非常多的好处,在面对对象的编程使用类和结构体是一个更快更直接的方法,使用模板时,就有两个步骤 ︰ 定义模板并创建对象使用的模板。

上面的代码是非常简单的。它没有功能,只是封装了类和结构,但它证明了即使是最简单的模板也可以减少变化的影响。那么这个时候我们来删除一些数据就非常方便了。
从产品类中删除属性

class Product {
    var name:String;
    var price:Double;
    var stock:Int;
    init(name:String, price:Double, stock:Int) {
        self.name = name;
        self.price = price;
        self.stock = stock;
} }

这里是删除了详情说明

var products = [
    Product(name: "Kayak", price: 275, stock: 10),
    Product(name: "Lifejacket", price: 48.95, stock: 14),
    Product(name: "Soccer Ball", price: 19.5, stock: 32)]

func calculateTax(product:Product) -> Double {
    return product.price * 0.2;
}

func calculateStockValue(productsArray:[Product]) -> Double {
    return productsArray.reduce(0, {
        (total, product) -> Double in
            return total + (product.price * Double(product.stock))
}); }

print("Sales tax for Kayak: $\(calculateTax(products[0]))");
print("Total value of stock: $\(calculateStockValue(products))");

现在的代码失去了详情的说明。但是calculateTax 和 calculateStockValue 在所有,并且每个功能的因为每个类中的属性是定义和访问独立于其他属性既不函数依赖于描述属性。
使用类和结构限制只是直接受到影响的变化,防止使用不太结构化的数据类型。

封装的好处

从数据对象作为模板,使用类或结构中最重要的好处是对封装的支持。
封装是面向对象编程的核心思想之一,有这种想法的轴承对这一章的两个方面。
第一个方面是,封装允许的数据值和操作这些值将被结合到单个组件中的逻辑。结合数据和逻辑使得它易于阅读的代码,因为一切有关的数据类型定义在相同的地方。现在要加上一些逻辑。
在 Product.swift File 中添加逻辑

class Product {
    var name:String;
    var price:Double;
    var stock:Int;
    init(name:String, price:Double, stock:Int) {
        self.name = name;
        self.price = price;
        self.stock = stock;
}
    //计算汇率的方法
    func calculateTax(rate: Double) -> Double {
        return self.price * rate;
}
    //股票价值
    var stockValue: Double {
        get {
            return self.price * Double(self.stock);
        }
}
}

我增加了calculateTax方法,它接受税率作为参数,用它来计算销售税,和stockValue计算得出的特性,实现 getter 子句,计算该股票的总价值。
我更新的代码语句 main.swift 文件和操作产品反对使用新的方法和属性,更新 main.swift 文件中的代码

var products = [
    Product(name: "Kayak", price: 275, stock: 10),
    Product(name: "Lifejacket", price: 48.95, stock: 14),
    Product(name: "Soccer Ball", price: 19.5, stock: 32)]

func calculateStockValue(productsArray:[Product]) -> Double {
    return productsArray.reduce(0, {(total, product) -> Double in
            return total + product.stockValue}) 
}

print("Sales tax for Kayak: $\(products[0].calculateTax(0.2))");
print("Total value of stock: $\(calculateStockValue(products))");

这些看似简单的变化,但一些重要的事情发生了︰
产品类现在有public的演示文稿和私有的实现

产品类的publicprivate方面
public是其他组件可以使用的 API。任何组件可以获取或设置的值名称, 价格,和股票属性在任何他们需要的方式中使用它们。
public还包括 stockValue 属性和 calculateTax 方法,但是 — — 这是重要的部分 — — 不是它们的实现。
提示不要混淆使用的私有实现想法private关键字。private关键字限制可以使用的类、 方法或属性,但即使当private不使用关键字、 实施方法和计算的属性不可见到调用组件。
能力呈现属性或方法,而又不暴露其执行容易打破紧密耦合,因为它是不可能的另一组件将取决于执行。作为一个例子,我是如何改变的执行 calculateTax 方法来定义最大的税额。因为在执行执行计算产品对象,变化是肉眼看不到其他元件,因此相信产品类知道如何执行其计算。
改变在 Product.swift File 中的方法实现

func calculateTax(rate: Double) -> Double {
    return min(10, self.price * rate);
}

我用函数从swift的标准库,设置了销售税 10 美元的最高限量。我有只显示 calculateTax 方法在清单,因为没有其他代码语句在playground上不得不改变,以适应新的税计算; 变化是在私有实现部分的产品类,与其他组件都无法创建依赖项。运行该应用程序会产生以下结果 ︰

皮划艇的销售税: $10.0
总价值的股票: $4059.3

扩展public的好处
A 的 Swift 不错的功能是你可以进化随着时间流逝,更改应用程序类的public演示文稿的方式。由此看来, 股票属性是一个标准的存储的属性,可以设置为任何 Int 值,但它没有意义的股票,有负的项目数,这样做会影响所返回的结果 stockValue 计算属性。
swift允许我无缝地替代股票-存储属性计算的属性,其实现可以强制验证策略,以确保库存水平是永远不会小于零。在 Product.swift File 中添加一个计算的属性

class Product {
var name:String;
var price:Double;
private var stockBackingValue:Int = 0;
    var stock:Int {
        get {
            return stockBackingValue;
        }
        set {
            stockBackingValue = max(0, newValue);
} }
    init(name:String, price:Double, stock:Int) {
        self.name = name;
        self.price = price;
        self.stock = stock;
}
    func calculateTax(rate: Double) -> Double {
        return min(10, self.price * rate);
}
    var stockValue: Double {
        get {
            return self.price * Double(self.stock);
        }
} }

我定义了一个支持变量将保存的值股票属性和已经取代了存储股票具有计算具有属性的属性 getter 和 setter 的属性。吸气剂只是返回属性的值的支持,我有被命名为 stockBackingValue,但二传手使用从标准库将支持值设置为零,当负的值用来设置该属性的最大功能。这种变化的影响的确是的public和private部分的产品类有改变,但这不会影响代码的方式,使用的类

存储的属性更改为计算属性的影响在 main.swift 文件中检查验证

var products = [
    Product(name: "Kayak", price: 275, stock: 10),
    Product(name: "Lifejacket", price: 48.95, stock: 14),
    Product(name: "Soccer Ball", price: 19.5, stock: 32)];
func calculateStockValue(productsArray:[Product]) -> Double {
    return productsArray.reduce(0, {(total, product) -> Double in
}
    return total + product.stockValue;
}); 
print("Sales tax for Kayak: $\(products[0].calculateTax(0.2))"); 
print("Total value of stock: $\(calculateStockValue(products))"); 
products[0].stock = -50;
print("Stock Level for Kayak: \(products[0].stock)");

我在playground上测试末尾添加两个语句股票属性的负值,但没有其他变化处理能力是必需的。尤其是,代码语句,依靠股票属性都不知道从存储的属性为一个计算的变化。下面是示例应用程序运行时产生的控制台输出 ︰

皮划艇的销售税: $10.0
总价值的股票: $4059.3
库存水平的皮划艇 ︰ 0
最后一条消息显示计算属性的效果 ︰ 我设置股票属性设置为-50,但该属性值的时候,我收到0.

理解的模式的陷阱

要避免的缺陷与此模式选择模板,错误的这通常意味着要使用结构,当一个类会更合适。swift的类和结构有很多共同之处,但还有一个重要的区别,
在这种模式下 ︰ 结构是值对象和类的引用对象。
在可可对象模板模式的示例
因为这是一个基本的模式,类和结构可以发现整个可可框架和内置swift类型。作为结构,实现基本的类型,如字符串、 数组和字典和类用于表示一切从网络连接到用户界面组件。我不会对列表中所有的类和结构,用于 iOS 和可可框架,但是如果你想要如何一种深深植根这种模式是在 iOS 开发,看一看我用于创建 SportsStore 应用程序的类。除了产品类我创建了在这一章,我有依靠 NSNumberFormatter 格式化货币字符串 UIViewController 来管理应用程序和类,如所提的意见 UILabel, UITextField,和 UIStepper 来预设布局组件向用户。
将模式应用到 SportsStore 应用程序
准备示例应用程序
创建一个文件,我将使用定义并不直接相关,设计模式的实用功能。要将新文件添加到项目,控制-点击 SportsStore 文件夹项目导航器中,从菜单中选择新的文件。Xcode 将呈现不同的文件类型,

选择从 iOS 的 Swift 文件➤源类别并单击下一步按钮。设置文件名称为 Utils.swift ,并确保 SportsStore 在目标列表中,选中,

创建的 Product.swift 文件
Xcode 将创建新文件并打开它进行编辑。 如何使用该文件来定义 Utils 类。
Utils.swift File 的内容

import Foundation;
class Utils {
    class func currencyStringFromNumber(number:Double) -> String {
        let formatter = NSNumberFormatter();
        formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle;
        return formatter.stringFromNumber(number) ?? "";
} }

我定义了调用的方法类型 (也称为静态方法)currencyStringFromNumber接受双值和数字格式设置为货币值的回报。例如,值 1000.1 将格式化为字符串 $1,000.10。(货币符号是基于应用的设备的区域设置。在美国,美元符号可能被替换另一个符号,如那些为欧元或英镑。)
字符串的格式不是我描述在此表中,所以我已经定义在此代码的模式的一部分Utils.swift要保持它的出路的文件。
当我将添加到底部的 SportsStore 布局标签所显示的信息,我将使用新的类型方法。

创建产品类
正如我在"理解 Swift 访问控制"边栏,解释private关键字限制访问并不在同一个类文件中定义的代码。因为我想要强调由这种模式提供的公钥/私钥分离,要创建一个新文件并使用它来定义产品类。在上一节中描述的过程添加一个文件称为 Product.swift SportsStore 项目并使用它来定义类。
Product.swift File 的内容

class Product {
    private(set) var name:String;
    private(set) var description:String;
    private(set) var category:String;
    private var stockLevelBackingValue:Int = 0;
    private var priceBackingValue:Double = 0;
    init(name:String, description:String, category:String, price:Double,
        stockLevel:Int) {
            self.name = name;
            self.description = description;
            self.category = category;
            self.price = price;
            self.stockLevel = stockLevel;
}
    var stockLevel:Int {
        get { return stockLevelBackingValue;}
        set { stockLevelBackingValue = max(0, newValue);}
}
    private(set) var price:Double {
        get { return priceBackingValue;}
        set { priceBackingValue = max(1, newValue);}
}
    var stockValue:Double {
        get {
            return price * Double(stockLevel);
        }
} } 

提出了重点是public的表示与private实施,取得了在几种方法的分离所示的类。第一种方法是通过对添加注释的属性与private或 private(set)。private关键字可以隐藏不管它适用于从代码在当前文件,及这效果的制作 priceBackingValue 和 stockLevelBackingValue 属性完全看不到 SportsStore 应用的其余部分因为产品类是唯一永恒的东西 Product.swift 文件。
注释具有的属性private(set)意味着属性可以从同一模块中的其他文件中的代码读取,但只有在由代码设置Product.swift文件。我用 private(set) 的属性在清单 4-13,大部分具有允许,否则为不得设置使用传递给类初始值设定项的参数值的效果。
提示可有类似的效果,使用常量,但是我想要强调在这一章,对象模板模式和private(set)是一个更有用的例子。
我用另一个方法是计算属性,该属性定义了只有获取子句。计算属性的实现是private的即使该属性本身是整个当前模块可用。

了解快速访问控制

swift以不寻常的方法来访问控制,能够找出粗心。有三个级别的访问控制,这使用应用public, private,和internal关键字。private关键字是限制性最强的; 它限制对类、 结构方法和属性的定义在同一文件中的代码访问。限制访问每个文件的基础上从大多数语言的不同方法,并且意味着private在 Xcode 没有影响
儿童游乐场。
internal关键字表示当前模块内允许访问。这是默认级别的访问控制,如果没有关键字应用使用。对于大多数的 iOS 开发, internal保护会允许类、 结构、 方法、 函数或属性用于整个项目的效果。
public关键字适用限制性最小的控制级别,并允许从任何地方,包括当前模块外部访问。这是给开发商谁正在创建框架和谁将需要使用的大多数使用的public关键字定义的框架给其他开发人员提供了 API。
如果你已经搬到swift从一种语言如 C# 或 Java,然后你可以最密切重新创建你习惯于通过定义每个类或结构在其自己的访问控制.swift文件和使用private和internal的访问级别。

ViewController.swift File 中的产品类

import UIKit
class ProductTableCell : UITableViewCell {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var stockStepper: UIStepper!
    @IBOutlet weak var stockField: UITextField!
    var product:Product?;
}
class ViewController: UIViewController, UITableViewDataSource {
    @IBOutlet weak var totalStockLabel: UILabel!
    @IBOutlet weak var tableView: UITableView!
    var products = [
        Product(name:"Kayak", description:"A boat for one person",
            category:"Watersports", price:275.0, stockLevel:10),
        Product(name:"Lifejacket", description:"Protective and fashionable",
            category:"Watersports", price:48.95, stockLevel:14),
        Product(name:"Soccer Ball", description:"FIFA-approved size and weight",
            category:"Soccer", price:19.5, stockLevel:32),
        Product(name:"Corner Flags",
            description:"Give your playing field a professional touch",
            category:"Soccer", price:34.95, stockLevel:1),
        Product(name:"Stadium", description:"Flat-packed 35,000-seat stadium",
            category:"Soccer", price:79500.0, stockLevel:4),
        Product(name:"Thinking Cap",
            description:"Improve your brain efficiency by 75%",
            category:"Chess", price:16.0, stockLevel:8),
        Product(name:"Unsteady Chair",
            description:"Secretly give your opponent a disadvantage",
            category: "Chess", price: 29.95, stockLevel:3),
        Product(name:"Human Chess Board",
            description:"A fun game for the family", category:"Chess",
            price:75.0, stockLevel:2),
        Product(name:"Bling-Bling King",
            description:"Gold-plated, diamond-studded King",
            category:"Chess", price:1200.0, stockLevel:4)];
 
override func viewDidLoad() {
    super.viewDidLoad()
    displayStockTotal();
}
override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView,
    numberOfRowsInSection section: Int) -> Int {
    return products.count;
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let product = products[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("ProductCell") as ProductTableCell
cell.product = products[indexPath.row]
cell.nameLabel.text = product.name
cell.descriptionLabel.text = product.description
cell.stockStepper.value = Double(product.stockLevel)
cell.stockField.text = String(product.stockLevel)
return cell;
}
@IBAction func stockLevelDidChange(sender: AnyObject) {
    if var currentCell = sender as? UIView {
        while (true) {
            currentCell = currentCell.superview!;
            if let cell = currentCell as? ProductTableCell {
                if let product = cell.product? {
                    if let stepper = sender as? UIStepper {
                        product.stockLevel = Int(stepper.value);
                    } else if let textfield = sender as? UITextField {
                        if let newValue = textfield.text.toInt()? {
                            product.stockLevel = newValue;
} }
                    cell.stockStepper.value = Double(product.stockLevel);
                    cell.stockField.text = String(product.stockLevel);
}
break; }
}
        displayStockTotal();
    }
} 
func displayStockTotal() {
let stockTotal = products.reduce(0,
{(total, product) -> Int in return total + product.stockLevel}); totalStockLabel.text = "\(stockTotal) Products in Stock";
} }

过渡到使用产品类是简单的。在编写代码清单 4-14,我开始用在类产品数据数组,然后修正所有的编译器错误,直到被替换了对元组的所有引用。这是一个乏味而且容易出错的过程,这就是为什么它是一个好的主意,开始与类和结构的一个项目,如果你能 (某物,可悲的是,并不总是可行时接管现有的代码)。
确保视图和模型分离
有几个点要注意有关清单 4-14 中的代码。第一个是,
ViewController.swift文件定义了一个名为类ProductTableCell用于包含表示应用程序布局中的产品的 UI 组件的引用,并找到一种产品,当用户更改库存水平。在清单 4-14,我取代提到一个元组中的索引位置变量产品数组引用产品对象相反,像这样 ︰

class ProductTableCell : UITableViewCell {
   @IBOutlet weak var nameLabel: UILabel!
   @IBOutlet weak var descriptionLabel: UILabel!
   @IBOutlet weak var stockStepper: UIStepper!
   @IBOutlet weak var stockField: UITextField!
   var product:Product?;
}

你可能想知道为什么我没有结合ProductTableCell与产品类,并有一个单一的实体表示产品和用来显示它的 UI 组件。我解释第 5 部分详细的原因,当我描述模型,视图,控制器 (MVC) 模式,但简短的回答是很好的实践,它呈现给用户 (在 MVC 的说法,将模型与视图分离) 的方式从应用程序中对数据进行分离。执行这种分离允许更容易地以不同的方式显示相同数据。我可能需要第二个视图添加到应用程序,在网格中,提出了产品和不经分离模型和视图之间,组合的类需要能够对每个参与这两种观点的很快变得笨拙 UI 组件的引用并使应用更改一个棘手且容易出错的过程。
扩大摘要显示
我一直在批评的元组在整个这一章,但他们可以当他们使用在自足的方式,而不是代表整个应用程序范围的数据的一种有用的语言功能。
你可以看到一个例子的怎样使用元组。
我已经改变了的执行 displayStockTotal 方法的 ViewController 类,以便单个调用到全球减少函数用于计算股票中的项目数和该股票的总价值 (其中使用设置格式 currencyStringFromNumber 方法定义在清单 4-12)。
在 ViewController.swift File 中使用元组

...
func displayStockTotal() {让 finalTotals:(Int, Double) = products.reduce (0 (0.0),
        {(totals, product)-> (Int,Double) in
            return (
                totals.0 + product.stockLevel,totals.1 product.stockValue +
            );
        });
             totalStockLabel.text ="\(finalTotals.0) 在库存中的产品"。
        +"总价值 ︰ \(Utils.currencyStringFromNumber(finalTotals.1))";
} ...

元组让我产生两个总计值 (一个用于股票中的项目数),一个为这支股票的价值为每次迭代中减少函数。能取得这以不同的方式 — — 如通过定义一个结构,具有两个属性,或通过使用为循环来枚举数组并更新两个局部变量 — — 但使用元组与swift关闭很好地工作和生产是简单和易读的代码。这种使用,创建一个类或结构将会矫枉过正,因为外面的方法,导出数据,不是元组的下怀,不会引起出现传递更广泛地在应用程序中的元组时的紧耦合和维护问题。
你可以看到额外的总数的效果我通过启动应用程序来计算。该标签底部的布局将显示的数量和项目价值的股票

摘要
在本章中,我描述了一种模式,在swift发展的核心是 ︰ 定义一个用于创建对象的模板。这种模式的好处是,它提供了基本的工具,可以用来打破紧耦合的组件分开,允许一个public API 来呈现给消费者的一个对象和一个隐藏的私有实现。
在下一章,我转向创建对象的不同的方式 ︰ 使用原型.

你可能感兴趣的:(swift中的封装设计模式)