//
//  Paradigm.swift
//  SwiftCode
//
//  Created by Alisa on 2022/4/13.
//  Copyright © 2022 Alisa. All rights reserved.
//

import Foundation

/*

** 泛型的作用与使用场景
 
    泛型通常用来表达一种未定的数据类型
 
    泛型的作用:范型是程序设计语言的一种特性,允许开发者在强类型程序设计语言中编写代码时定义一些可变部分
 
    泛型的一个使用场景:
        我们需要编写一个函数,如果这个函数有参数,通常我们需要明确参数的类型。现在我们要编写这样一个函数,其功能将对两个inout且类型相同的参数进行值的交换,这个函数的功能需求并没有要求参数的具体类型,只是要求类型相同,对于这类需求,开发者不会也无法使用重载的方式将所有数据类型对应的函数都实现一遍,而泛型刚好可以解决这类问题
 
** 泛型在函数与集合中的使用
    
    泛型作为函数参数:在函数参数列表前使用<>,将要定义的泛型类型列出,其作用域为函数的参数列表与整个函数实现部分,如果要在一个函数中定义多个泛型,
 使用逗号进行分隔即可。
 
    泛型在集合中的使用:在声明Array或Dictionary这类集合类型时,开发者可以同时设置这些集合类型中所要存放的元素类型,我们也可以通过泛型来实现
 
** 对泛型进行约束
    swift中可以通过两种方式对泛型进行约束:
        <1> 通过继承基类或者遵守协议来进行约束
        <2> 通过where子句来进行约束
 
    使用继承的方式约束泛型的类型,约束该泛型必须通过某一基类或者继承于某一基类的子类实现
 
    使用遵守协议的方式约束泛型,协议中可以定义一些没有实现的方法和属性,遵守这个协议的类型需要对其中定义的方法和属性进行实现
    在协议中泛型还有特殊的应用,在创建协议时,可以使用associatetype关键字进行泛型类型的关联,当有数据类型实现此协议时,这个关联的泛型的
 具体类型才会被指定
 
    使用where子句与泛型进行结合,可以为泛型添加更加严格的类型约束
 
*/

class FirstAcquaintance{
    
    //使用泛型,定义一个函数,使传入的两个值进行值的互换
    func exchange<T>(param1: inout T, param2: inout T){
        let tmp = param1
        param1 = param2
        param2 = tmp
        print("互换后的数据:param1: \(param1), param2: \(param2)")
    }
}

//使用泛型定义元素的类型,实现一个简单的栈功能,栈的特性是:先进后出
struct Stack<ItemType>{
    
    //内部有关元素类型的操作都使用ItemType
    var items:[ItemType] = []
    mutating func push(param:ItemType){
        self.items.append(param)
        print("入栈后的数组:\(self.items)")
    }
    
    //mutating修饰函数用于改变值类型的属性,但只能用于修饰结构体的实例方法
    mutating func pop()->ItemType{  //用于修饰class的方法时:'mutating' is not valid on instance methods in classes
        
        return self.items.removeLast()
    }
}

//使用继承的方式约束泛型
class Bread{
    var typeName:String
    func bakeInOven(){
        print("This bread will bake into the oven.")
    }
    init(typeName:String) {
        self.typeName = typeName
    }
}
struct Oven<ItemType:Bread>{
    var bread:ItemType
    mutating func baking(){
        print("把面包:\(bread.typeName)放到烤箱中烤制")
    }
}

//使用协议的方式约束泛型
protocol Food{
    //实现协议时才指定类型
    associatedtype ItemType
    //协议中约定一个ItemType类型的计算属性
    var food:ItemType {get set}
    //协议中约定一个方法
    func heatFoodInPot(param:ItemType)->Void
    
    //注意
    //协议中约定一个值属性,这种直接声明的值属性是不可以的,必须使用计算属性的方式
    //var foodName:ItemType   //Property in protocol must have explicit { get } or { get set } specifier
}
class Milk{
    var name:String = "Milk"
}
//定义一个遵守Food协议的类
class Pot:Food{
    var food:Milk = Milk()
    var foodType:Milk{
        get{
            return self.food
        }
        set{
            self.food = newValue
        }
    }
    func heatFoodInPot(param: Milk) {
        print("Now heat the \(food.name) in this pot!")
    }
}

//使用where子句对泛型进行约束
class WhereBox<B,M> where B:Bread, M:Milk{
    var foodOne:B
    var foodTwo:M
    init(foodOne:B, foodTwo:M){
        self.foodOne = foodOne
        self.foodTwo = foodTwo
    }
    func boxFood(){
        print("This box has \(foodOne.typeName) and \(foodTwo.name)")
    }
}

class Paradigm{
    
    //泛型作为函数的参数
    func useInMethod(){
        var p1 = "15"
        var p2 = "40"
        let fav = FirstAcquaintance()
        fav.exchange(param1: &p1, param2: &p2)
        //打印数据:互换后的数据:param1: 40, param2: 15
    }
    
    //泛型定义元素的类型
    func useInElement(){
        
        //整型栈
        var one = Stack<Int>()
        one.push(param: 2)
        one.push(param: 4)
        one.push(param: 6)
        one.push(param: 8)
        one.push(param: 10)
        one.push(param: 12)
        one.push(param: 14)
        
        let oneA = one.pop()
        print("整型栈:出栈的元素:\(oneA), 出栈后的数组:\(one.items)")
        
        //字符串栈
        var two = Stack<String>()
        two.push(param: "one")
        two.push(param: "two")
        two.push(param: "three")
        two.push(param: "four")
        two.push(param: "five")
        two.push(param: "six")
        
        let twoA = two.pop()
        print("字符串栈:出栈的元素:\(twoA), 出栈后的数组:\(two.items)")
        
        /* 打印数据:
         入栈后的数组:[2]
         入栈后的数组:[2, 4]
         入栈后的数组:[2, 4, 6]
         入栈后的数组:[2, 4, 6, 8]
         入栈后的数组:[2, 4, 6, 8, 10]
         入栈后的数组:[2, 4, 6, 8, 10, 12]
         入栈后的数组:[2, 4, 6, 8, 10, 12, 14]
         整型栈:出栈的元素:14, 出栈后的数组:[2, 4, 6, 8, 10, 12]
         入栈后的数组:["one"]
         入栈后的数组:["one", "two"]
         入栈后的数组:["one", "two", "three"]
         入栈后的数组:["one", "two", "three", "four"]
         入栈后的数组:["one", "two", "three", "four", "five"]
         入栈后的数组:["one", "two", "three", "four", "five", "six"]
         字符串栈:出栈的元素:six, 出栈后的数组:["one", "two", "three", "four", "five"]
        */
    }
    
    //使用继承的方式约束泛型
    func useInheritance(){
        
        let bread = Bread(typeName: "Buttered Toast")
        bread.bakeInOven()
        var oven = Oven(bread: bread)
        oven.baking()
        /* 打印信息
         This bread will bake into the oven.
         把面包:Buttered Toast放到烤箱中烤制
        */
    }
    
    //使用协议的方式约束泛型
    func useProtocol(){
        
        let milk = Milk()
        milk.name = "Milk Deluxe"
        
        let pot = Pot()
        pot.foodType = milk
        pot.heatFoodInPot(param: pot.foodType)
        
        /* 打印信息
         Now heat the Milk Deluxe in this pot!
        */
    }
    
    //使用where子句对泛型进行约束
    func useWhere(){
        
        let bread = Bread(typeName: "Buttered Toast")
        let milk = Milk()
        milk.name = "Milk Deluxe"
        
        let wh = WhereBox(foodOne: bread, foodTwo: milk)
        wh.boxFood()
        
        /* 打印信息
         This box has Buttered Toast and Milk Deluxe
        */
    }
}