OC基础--多态 及 三特性小练习

时间:2023-03-10 01:16:03
OC基础--多态 及 三特性小练习

什么是多态

  什么是多态:

    多态就是某一类事物的多种形态    

      猫: 猫-->动物

      狗: 狗-->动物

      男人 : 男人 -->人 -->动物

      女人 : 女人 -->人 -->动物

    多态表示不同的对象可以执行相同的动作, 但是通过他们自己的实现代码来执行

  程序中的多态:父类指针指向子类对象

多态的条件

  有继承关系

  子类重写父类方法

  父类指针指向子类对象
         
狗 *g = [狗 new];
         
动物 *a = [狗
new];
          猫 *c = [猫 new];
          动物 *a = [猫 new];

  表现:当父类指针指向不同的对象的时候,通过父类指针调用被重写的方法的时候,会执行该指针所指向的那个对象的方法

多态的优点

  多态的主要好处就是简化了编程接口。它允许在类和类之间重用一些习惯性的命名,而不用为每一个新的方法命名一个新名字。这样,编程接口就是一些抽象的行为的集合,从而和实现接口的类的区分开来。

  多态也使得代码可以分散在不同的对象中而不用试图在一个方法中考虑到所有可能的对象。
这样使得您的代码扩展性和复用性更好一些。当一个新的情景出现时,您无须对现有的代码进行 改动,而只需要增加一个新的类和新的同名方法。

多态的原理

  动态绑定:

    动态类型能使程序直到执行时才确定对象的真实类型

    动态类型绑定能使程序直到执行时才确定要对那个对象调用的方法

  OC可以在运行时加入新的数据类型和新的程序模块:动态类型识别,动态绑定,动态加载

  id类型:通用对象指针类型,弱类型,编译时不进行具体类型检查

  补充:

    动态数据类型:    
在编译的时候编译器并不知道变量的真实类型,
只有在运行的时候才知道它的真实类型  并且如果通过动态数据类型定义变量, 如果访问了不属于动态数据类型的属性和方法, 编译器不会报错

    静态数据类型:    
默认情况下所有的数据类型都是静态数据类型, 在编译时就知道变量的类型, 知道变量中有哪些属性和方法 , 在编译的时候就可以访问这些属性和方法, 并且如果是通过静态数据类型定义变量, 如果访问不了属于静态数据类型的属性和方法, 那么编译器就会报错

    里氏替换原则:    子类对象能够替换其父类对象被使用。就是说“子类是父类”,比如,人是动物,但动物不一定是人,有一个Animal类和一个继承自Animal类的Person类, 
这时, Animal类型的变量可以指向Person类的实例, 但是Person类类型的变量不能指向Animal类的实例!

    开放封闭原则:软件实体(类  模块  函数等等) 应该可以扩展,但是不可修改. 这个原则其实是有两个特征:

      一个是说"对于扩展是开放的(Open for extension) " : 意味着有新的需求时,可以对已有的软件实体进行扩展,以满足新的需求

      一个是说"对于更改是封闭的(Closed for
modification)"  : 意味着软件实体一旦设计完成,就可以独立完成其工作,而不要对其进行任何修改。

多态的实现

  人喂宠物吃东西的例子:

    人(Person)  
行为: 喂宠物吃东西(feedPet)

    宠物(Pets)   行为: 吃东西(eat)

    猫(Cat)        
行为: 吃东西(eat)

    狗(Dog)       
行为: 吃东西(eat)

    (猫类和狗类 继承自宠物类)

  示例代码:

    宠物类的声明实现:

      #import <Foundation/Foundation.h>
      @interface Pets : NSObject
      // 吃东西
      - (void) eat;
      @end       #import "Pets.h"
      @implementation Pets
      // 吃东西
      - (void)eat{
       NSLog(@"宠物吃东西");
      }
      @end

    猫类的声明实现:

            #import "Pets.h"
@interface Cat : Pets
// 猫类特有的方法 抓老鼠
- (void) catchMouse;
@end #import "Cat.h"
@implementation Cat
// 重写父类吃东西方法
- (void)eat{
NSLog(@"人喂宠物猫吃东西");
}
       // 实现猫类特有的方法 抓老鼠
        - (void)catchMouse{
        NSLog(@"老猫抓老鼠");
       }
      @end

    狗类的声明实现:

      #import "Pets.h"
      @interface Dog : Pets
      @end       #import "Dog.h"
      @implementation Dog
      // 重写父类吃东西方法
      - (void)eat{
       NSLog(@"人喂宠物狗吃东西");
      }
      @end

    人类的声明实现:      

      #import "Pets.h"
      @interface Person : NSObject
      // 喂宠物吃东西
      + (void) feedPet:(Pets *) pet;
      @end       #import "Person.h"
      @implementation Person
      // 喂宠物吃东西
      + (void)feedPet:(Pets *)pet{
       [pet eat];
      }
      @end

    Main.m :

      #import <Foundation/Foundation.h>
      #import "Person.h"
      #import "Pets.h"
      #import "Dog.h"
      #import "Cat.h"       int main(int argc, const char * argv[]) {
       // 多态的体现
       Pets * pet = [Pets new];
       [Person feedPet:pet];        pet = [Dog new];
       [Person feedPet:pet];
      
       pet = [Cat new];
       [Person feedPet:pet];        return ;
      }

    输出结果:      

       /*
2015-08-31 18:10:06.659 多态示例[833:53348] 宠物吃东西
       2015-08-31 18:10:06.660 多态示例[833:53348] 人喂宠物狗吃东西
       2015-08-31 18:10:06.660 多态示例[833:53348] 人喂宠物猫吃东西
*/

  》在上面代码中我们将Dog 和 Cat的实例赋值给了Pets类型的变量 pet  , 即父类类型的指针指向了子类的对象, 这便是里氏替换原则的体现

  》我们给Person类定义了一个类方法 :   + (void) feedPet:(Pets) pet;  这个方法是接收一个Pets类型的对象作为参数, 然后再方法体里通过传进来的对象调用吃的方法(eat), 我们给feedPet方法传递的参数都是Pets类型的变量 pet,  但是通过输出结果可以知道, 实际上是分别调用了宠物   狗 和 猫  的吃的方法  也就是说:当父类指针指向不同的对象的时候,通过父类指针调用被重写的方法,会执行该指针所指向的那个对象的方法, 这就是多态

多态注意点

   
父类指针指向子类对象, 如果需要调用子类特有的方法, 必须先强制类型转换为子类才能调用

    Pets * pet = [Cat new];
// 错误信息: No visible @interface for 'Pets' declares the selector 'catchMouse'
[pet catchMouse];
// 类型转换后在调用方法
[(Cat *)pet catchMouse];

封装继承多态练习(示例源于 大话设计模式  程杰著)

实现一个计算机控制台程序   要求输入两个数和操作符   输出结果

  1.0版:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
double a =, b = , c = ;
char d = '+';
if(d == '+'){
c = a + b;
}
if( d == '-'){
c = a - b;
}
if( d == '*'){
c = a * b;
}
if( d == '/'){
c = a / b;
}
NSLog(@" 运算结果:%.2lf", c);
return ;
}

  结论:

    "代码无错就是优" 能获得想要的结果  挺好的嘛

  问题:

   1: 变量命名不规范   a b c d  没啥意义  看着名字也不知道是用来干嘛的

   2: 注释都没有  程序只有自己暂时能看懂

   3: 每个if都要判断一遍

    4: 做除法时如果除以 0 会怎么样

  2.0版:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
// 用于计算的两个数字和运算符应有用户输入 此处写死在程序中
double num1 = , num2 = , result = ;
char operate = '+';
// 根据输入的运算符选择运算方式并获得运算结果
switch (operate) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
if (num2 != ) {
result = num1 / num2;
}
else{
NSLog(@"除数不能为 0"); // 是除数还是被除数啊 忘了
}
break;
}
NSLog(@"运算结果是: %.2lf", result); return ;
}

  结论:

    好了  变量名像点样了  也不会做没必要的判断了  还能得到想要的结果  这总没问题了吧

  书摘:

    "碰到问题就直觉的用计算机能够理解的逻辑来描述和表达待解决的问题和具体的求解过程,这本身没有错, 但这样的思维却使得我们的程序只为满足实现当前的需求, 程序不容易维护, 不容易扩展, 更不容易复用,从而达不到高质量代码的需求"

  问题:

    现在的计算器只是控制台的输出  如果要做个桌面程序  或者网站上的计算器  或者是手机应用怎么办??  把代码拷贝过去?? 怎么让代码复用呢

  3.0版:

  Operation类声明文件:

// 添加一个Operation运算类
#import <Foundation/Foundation.h> @interface Operation : NSObject
// 传入两个数字 和 一个操作符 获得运算结果
+ (double) getResultOfNum1:(double) num1 andNum2: (double) num2 withOperate:(char) operate;
@end

  Operation类实现文件:

#import "Operation.h"

@implementation Operation
// 根据传入的两个数字和运算符获得运算结果
+ (double)getResultOfNum1:(double)num1 andNum2:(double)num2 withOperate:(char)operate{
// 用于存储结果
double result = ;
// 选择运算方式并获得结果
switch (operate) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
if (num2 != ) {
result = num1 / num2;
}
else{
NSLog(@"除数不能为 0");
}
break;
}
// 返回运算结果
return result;
}
@end

  Main.m:

#import <Foundation/Foundation.h>
#import "Operation.h" int main(int argc, const char * argv[]) {
// 两个参与运算的数字 res用于存储运算结果
double num1 = , num2 = , res = ;
// 运算符
char ope = '*';
// 调用运算类的运算方法获得结果
res = [Operation getResultOfNum1:num1 andNum2:num2 withOperate:ope];
// 打印输出
NSLog(@"运算结果是:%.2lf", res); return ;
}

  结论:

    现在通过封装把计算和显示分开了  也就是实现了业务逻辑和界面逻辑的分离  这里主函数中的代码也精简了一些 不用去管方法里面到底是怎么得到结果的  运算类的代码也不用去动便可复用

  问题:

    但是 怎么让代码可以灵活的扩展呢? 比如要加一个求余 开方等运算进去,  我们现在需要改动的是在运算类的switch中添加分支就行了, 但是这样真的好吗? 只是加一个运算符却需要其他已经有的运算符都来参与编译

  书上举例:

    "现在公司要你维护薪资管理系统, 原本只有技术人员(月薪) 销售(底薪+提成) 经理(年薪+股份)这三种运算算法, 现在需要加一个兼职员工(日薪)的算法, 按照现在这种做法, 公司必须把包含原有三种算法的类交给你来修改, 如果一不小心将兼职员工工资算法加进去的同时,顺手把技术人员的月薪提高那么一丢丢, 是不是很爽呢, 总有人不爽的"

  优化:

    将加减乘除等运算做分离, 修改其中一个也不影响其他, 增加运算算法也不影响已有的算法, 这就是对扩展开放, 对修改封闭--开放-封闭原则

  4.0版:

  Operation类的声明实现:    

#import <Foundation/Foundation.h>
@interface Operation : NSObject
{
@public
double _number1;
double _number2;
}
- (void) setNumber1: (double) number1;
- (void) setNumber2: (double) number2;
// 获取运算结果
- (double) getResult;
@end #import "Operation.h"
@implementation Operation
- (void)setNumber1:(double)number1{
_number1 = number1;
}
- (void)setNumber2:(double)number2{
_number2 = number2;
}
// 获取运算结果
- (double)getResult{
double result = ;
return result;
}
@end

  加法类的声明实现:

#import "Operation.h"
// 加法运算类
@interface OperationAdd : Operation
@end #import "OperationAdd.h"
@implementation OperationAdd
// 获得两数相加结果
- (double)getResult{
double result = ;
result = _number1 + _number2;
return result;
}
@end

  减法类的声明实现:

#import "Operation.h"
// 减法运算类
@interface OperationSub : Operation
@end #import "OperationSub.h"
@implementation OperationSub
// 获得两数相减的结果
- (double)getResult{
double result;
result = _number1 - _number2;
return result;
}
@end

  乘法类的声明实现:

#import "Operation.h"
// 乘法运算类
@interface OperationMul : Operation
@end #import "OperationMul.h"
@implementation OperationMul
// 获得两数相乘的结果
- (double)getResult{
double result = ;
result = _number1 * _number2;
return result;
}
@end

  除法类的声明实现:

#import "Operation.h"
// 除法运算类
@interface OperationDiv : Operation
@end #import "OperationDiv.h"
@implementation OperationDiv
// 获得两数相除的结果
- (double)getResult{
double result = ;
if (_number2 != ) {
result = _number1 / _number2;
}
else{
NSLog(@"除数不能为 0");
}
return result;
}
@end

  工厂类的声明实现:

#import "OperationAdd.h"
#import "OperationSub.h"
#import "OperationMul.h"
#import "OperationDiv.h" // 专门用于获得运算类实例
@interface GetOperation : NSObject
+ (Operation *) createOperationWithOperate: (char) operate;
@end #import "GetOperation.h"
@implementation GetOperation
// 获得运算类实例
+ (Operation *)createOperationWithOperate:(char)operate{
// 多态的应用 父类指针指向子类实例对象
Operation * ope = nil;
// 根据传入的操作符获得相应的实例对象
switch (operate) {
case '+':
ope = [OperationAdd new];
break;
case '-':
ope = [OperationSub new];
break;
case '*':
ope = [OperationMul new];
break;
case '/':
ope = [OperationDiv new];
break;
}
return ope;
}
@end

  Main.m:

#import <Foundation/Foundation.h>
#import "GetOperation.h" int main(int argc, const char * argv[]) {
double num1 = , num2 = , res = ;
char operate = '*'; Operation * ope = [GetOperation createOperationWithOperate:operate];
ope.number1 = num1;
ope.number2 = num2; res = [ope getResult];
NSLog(@"运算结果是: %.2lf", res); return ;
}

   现在如果要修改其中一种运算 对其他的运算都不会有影响了  如果想要添加一种新运算  也只需要添加一个新运算类  然后在工厂方法中修改下switch分支就行了  不需要再提供原有的运算类  所以对其他已经存在的运算类都不会有影响   这样便实现了开放-封闭原则

书摘: "编程是一门技术,更是一门艺术,不能只满足于写完代码运行结果正确就完事,时常考虑如何让代码更加简练,更加容易维护,容易扩展和复用, 只有这样才可以真正得到提高"