Objective-C 基础教程第五章,复合

什么是复合?

编程中的复合(composition)就好像音乐中的作曲(composition)一样:将多个组件组合在一起,配合使用,从而得到完整的作品。

Car程序

接下来我们不再用shape作为例子来写代码了,这次用car作为例子写代码,我们先来看看如何搭建汽车模型。

1辆汽车只需要1台发动机和4个轮胎。

mainCarParts.m

#import <Foundation/Foundatin.h>

/*
汽车轮胎
*/
@interface Tire : NSObject
@end @implementation Tire
- (NSString*) description{
return (@"I' am a tire. I last a while.");
}
@end
/*
汽车发动机
*/
@interface Engine : NSObject
@end @implementation Engine
- (NSString*) description{
return (@"I am an engine. Vroom!");
}
@end
/*
* 汽车
*/
@interface Car: NSObject
{
//复合概念
Engine *engine;
Tire *tires[4]; //因为engine和tires是Car类的实例变量,所以他们是复合的。
//你可以说 汽车是由4个轮胎和一个发动机组成的。(但是人们一般不会这么说)
} //可以这么说 汽车有4个轮胎和一个发动机
-(void) print;
@end @implementation Car//Car的init方法创建了4个新轮胎 并赋值给tires数组,还创建了一台发送机并赋值给engine实例变量。
-(id) init{
if(self == [super init]){
engine = [Engine new];
tires[0] = [Tire new];
tires[1] = [Tire new];
tires[2] = [Tire new];
tires[3] = [Tire new];
}
return (self)
} -(void) print
{
NSLog(@"%@",engine);
for(int i=0;i<4;i++)
{
NSLog(@"%@",tires[i]);
}
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Car *car = [Car new];
[car print];
}
return 0;
}

Tips:(如果类中没有包含任何实例变量,便可以省略掉接口定义中的花括号。)

自定义NSLog()

NSLog可以通过%@来输出对象。NSLog处理%@说明符时,会询问参数列表中相应的对象以得到这个对象的描述。

技术原理上是NSLog给这个对象发送了description消息,然后对象的description方法生成了一个NSString并将其返回。在类中提供description方法就可以自定义NSLog()会如何输出对象。

存取方法get Set

编程人员很少会对自己编写的程序感到满意,因为软件开发是永无止境的,永远有bug要去修正。

我们可以使用存取方法来改进它,使它的代码更灵活。

存取(accessor)方法是用来读取或改变某个对象属性的方法。

settter存取方法之存方法。

getter存取方法之取方法。

我们在写代码的时候最好不要直接改变类实例变量的值,比如在main函数中car->engine改变值,应该使用setter方法改变,这也算是间接工作的一个例子。

@interface Car:NSObject
{
Engine *engine;
Tire *tires[4];
}
-(Engine *)engine;
-(void) SetEngine:(Engine *) newEngine;
-(Tire*) tireAtIndex:(int) index;
-(void) SetTire:(Tire*) tire atIndex:(int) index;
-(void) print;
@end
@implementation Car
- (Engine*) engine
{
return (engine);
}
-(void) SetEngine:(Engine*) newEngine
{
engine = newEngine;
}
@end //getter方法engine返回实例变量engine的当前值,Objective-C中所有对象间的交互都是通过指针实现的。
//setter方法让engine指针 指向了newEngine,
int main()
{
Engine *engine = [Engine new];
[car SetEngine:engine];
NSLog(@"the car's engine is %@",[car engine]);
}

Tires(轮胎) 存取方法

-(void) setTire:(Tire*) tire atIndex:(int) index;
-(Tire*) tireAtIndex:(int) index;

由于汽车的4个轮胎都有自己不同的位置,所以Car对象中包含一个轮胎的数组。

在这里我们需要用索引存取器而不能直接访问tires数组。

- (void)setTire:(Tire*) tire atIndex:(int) index
{
if( index < 0 || index > 3)
{
NSLog(@"bad index(%d) in setTire:atIndex:",index);
exit(1);
}
tire[index] = tire;
} - (Tire*) tireAtIndex:(int) index
{
if( index<0 || index>3)
{
NSLog(@"bad index(%d) in tireAtIndex:",index);
}
return (tires[index]);
}
Tire *tire = [Tire new];
[car setTire:tire atIndex:2];
NSLog(@"tire number two is %@",[car tireAtIndex:2]);

Car类代码的其他变化

Car类的init方法。因为我们有了能够访问engine和tire变量的方法,所以我们不再需要创建这两个变量了,可以直接从外部进行控制,(而是由创建汽车的代码来负责配置发动机和轮胎)。所以我们可以完全剔除init方法

ps:新车的车主会得到一辆没有轮胎和发动机的汽车,不过装配是轻而易举的事(有时软件中的生活比现实生活要容易的多啊 )。

#import <Foundtation/Foundation.h>
int main(int argc,const char*argv[])
{
//新车的车主购买一台新汽车
Car *car = [Car new]; //车主继续购买一个引擎 4个轮胎
Engine *engine = [Engine new];
//组装上引擎
[car setEngine:engine];
for(int i=0;i<4;i++)
{
Tire *tire = [Tire new];
//组装上轮胎
[car setTire:tire atIndex:i];
} //开上我们心爱的小车车
[car print];
return (0);
}

扩展Car程序

现在我们Car类已经有了存取方法,我们可以更加充分的利用它,来扩展我们的Car,比如我们可以给汽车弄个新的具体什么型号的发动机(Slant6),或者具体什么型号 牌子的轮胎(米其林)。

@interface Slant6 :Engine//新型发动机 继承自发动机接口
@end @implementation Slant6
- (NSString *) description
{
return (@"I am a slant- 6. VROOM! VROOM!");
}
@end
@interface MicheLin : Tire
@end @implementation MicheLin
- (NSString *)description
{
return (@"I am a tire , Extraordinary driving and driving experience(非凡驾控,持久体验)");
}
@end
#import <Foundation/Foundation.h>

int main(int argc,const char*argv[])
{
//新车的车主购买一台新汽车
Car *car = [Car new]; //车主继续购买一个Slant6引擎 4个米其林轮胎
Engine *engine = [Slant6 new];
//组装上引擎
[car setEngine:engine];
for(int i=0;i<4;i++)
{
Tire *tire = [MicheLin new];
//组装上轮胎
[car setTire:tire atIndex:i];
} //开上我们更加心爱的小车车
[car print];
}
2022-03-06 18:20:43.438697+0800 Car[13853:1512359] I am a slant- 6. VROOM! VROOM!
2022-03-06 18:20:43.439184+0800 Car[13853:1512359] I am a tire , Extraordinary driving and driving experience(非凡驾控,持久体验)
2022-03-06 18:20:43.439286+0800 Car[13853:1512359] I am a tire , Extraordinary driving and driving experience(非凡驾控,持久体验)
2022-03-06 18:20:43.439333+0800 Car[13853:1512359] I am a tire , Extraordinary driving and driving experience(非凡驾控,持久体验)
2022-03-06 18:20:43.439371+0800 Car[13853:1512359] I am a tire , Extraordinary driving and driving experience(非凡驾控,持久体验)

复合还是继承

我们的Car程序同时用到了继承和复合概念,也就是我们第一章和本章中所介绍的两个万能工具,那么什么时候用继承,什么时候用复合呢?这个问题问的不错。

  • 继承的类之间建立的关系为is a(是一个)。比如三角形是一个形状,Slant6是一个发动机,米其林是一种轮胎的名字。简单来说就是总类和一个细分类的关系。
  • 复合的类之间建立的关系为has a(有一个)。形状有一个填充颜色,汽车有一个发动机和4个轮胎。与继承不同,汽车不是一个发动机,也不是一个轮胎。简单来说就是它是由什么构成,它拥有什么,那么就可以用复合。

新手在面向对象编程时候经常会犯这样的错误:对任何东西都想使用继承,比如让Car类继承自Engine类,这样可是不行的。我们得套用公司:is s(是一个)的关系来建立面向对象编程继承的概念,蓝猫是一个猫,狸花猫是一个猫。这种就可以用继承的方式了。复合则套用has a(有一个)的公式,猫有一个嘴巴,两只眼睛,两只耳朵,一条尾巴等等。

实用的例子:这次假设你的程序可能会涉及有照车辆,就是需要某种执照才能合法驾驶的车辆。汽车、摩托车、牵引车都是有照车辆,套用公式is a(是一个):汽车是一个有照车辆、摩托车是一个有照车辆。所以可以用继承。创建一个类LicensedVehicle(有照车辆),再套用公式has a(有一个),有照车辆有一个牌照 (牌照所在地、牌照号码)(复合方式)。

//牌照
@interface Licenseplate
{
NSString Address;
NSString Number;
}
@end
//有照车辆
@interface LicensedVehicle : NSObject
{
Licenseplate licsplate; //复合概念
}
@end //继承概念
@interface Car :LicensedVehicle
@end @interface MotorCycle :LicensedVehicle
@end

小结

复合是OOP的基础概念,我们通过这种技巧来创建 引用其他类的对象,在本章中用了Car来做了很多复合的例子,还介绍了OC中比较重要的存取方法,存取方法和复合是密不可分的,我们以后应该规范化用settergetter方法。

最后还介绍了Cocoa存取方法的命名规则,不能使用get作为getter方法的名字,而是应该直接使用属性名,因为get名规则另有用途。

Objective-C 基础教程第五章,复合的更多相关文章

  1. python基础教程项目五之虚拟茶话会

    python基础教程项目五之虚拟茶话会 几乎在学习.使用任何一种编程语言的时候,关于socket的练习从来都不会少,尤其是会写一些局域网的通信的东西.所以书上的这个项目刚好可以练习一下socket编程 ...

  2. Objective-C 基础教程第三章,面向对象编程基础知

    目录 Objective-C 基础教程第三章,面向对象编程基础知 0x00 前言 0x01 间接(indirection) 0x02 面向对象编程中使用间接 面向过程编程 面向对象编程 0x03 OC ...

  3. Objective-C 基础教程第六章,源文件组织

    目录 Object-C 基础教程第六章,源文件组织 0x00:前言 0x01:Xcode创建OC类 0x02:Xcode群组 0x03 Xcode跨文件依赖关系 @class关键字 导入和继承 小结 ...

  4. Objective-C 基础教程第七章,深入理解Xcode

    目录 Object-C 基础教程第七章,深入理解Xcode 0x00 前言 0x01 创建工程界面 0x02 主程序界面 ①顶部 Top Test(测试) Profile(动态分析) Analyze( ...

  5. [ABP教程]第五章 授权

    原文档 地址: Web Application Development Tutorial - Part 5: Authorization 关于此教程 在这个教程系列中,您将构建一个基于ABP的Web应 ...

  6. Python机器学习基础教程-第2章-监督学习之决策树

    前言 本系列教程基本就是摘抄<Python机器学习基础教程>中的例子内容. 为了便于跟踪和学习,本系列教程在Github上提供了jupyter notebook 版本: Github仓库: ...

  7. Python机器学习基础教程-第2章-监督学习之线性模型

    前言 本系列教程基本就是摘抄<Python机器学习基础教程>中的例子内容. 为了便于跟踪和学习,本系列教程在Github上提供了jupyter notebook 版本: Github仓库: ...

  8. Python机器学习基础教程-第2章-监督学习之K近邻

    前言 本系列教程基本就是摘抄<Python机器学习基础教程>中的例子内容. 为了便于跟踪和学习,本系列教程在Github上提供了jupyter notebook 版本: Github仓库: ...

  9. Python机器学习基础教程-第1章-鸢尾花的例子KNN

    前言 本系列教程基本就是摘抄<Python机器学习基础教程>中的例子内容. 为了便于跟踪和学习,本系列教程在Github上提供了jupyter notebook 版本: Github仓库: ...

随机推荐

  1. 微信小程序入门教程之三:脚本编程

    这个系列教程的前两篇,介绍了小程序的项目结构和页面样式. 今天,接着往下讲,教大家为小程序加入 JavaScript 脚本,做出动态效果,以及如何跟用户互动.学会了脚本,就能做出复杂的页面了. 本篇的 ...

  2. java 中的多线程简单介绍

    package com.zxf.demo; /* * 多线程的实现方式两种? * 一..实现 runnable 接口 * 2.重写run方法 Run():当一个线程启动后,就会自动执行该方法 * 3. ...

  3. k8s-基础篇

    搭建k8s环境 Myapp镜像部署扩容pod自愈负载均衡DNS外网访问滚动更新YAML方式部署独立部署podRS副本控制器Deployment-自动扩容Deployment-更新版本Deploymen ...

  4. 天翼网关免密改桥接&恢复出厂(含修改超密工具)

    路由/桥接模式切换 说明:默认天翼网关用的局域网ip是192.168.1.1,如果不是,则修改为局域网ip. 本机已经是桥接模式,在这里可以输入宽带账号密码转换成路由模式,两个模式之间可以互转. 恢复 ...

  5. JS 函数提升&变量提升以及函数声明&函数表达式的区别

    感谢原文作者:迟早会有猫 原文链接:https://www.cnblogs.com/SidselLoong/p/10515809.html 今天看js的变量提升问题,里面提到了函数提升.然后发现自己之 ...

  6. Idea 中使用Lombok找不到其自动生成的方法

    问题描述 在我的Idea已经安装Lombok插件还有已经导入Lombok jar包依赖的情况下,仍然找不到其自动生成的方法. 问题分析 从各方大佬那里得知,Lombok通过Pluggable Anno ...

  7. Nginx 路由转发配置(转)

    Nginx 路由转发配置笔记 由于预算有限,只有一台服务器,想要玩的东西不少,所以这个台服务器上会提供多重服务,因此涉及到的nginx转发就必有重要了 由nginx做请求代理,提供多种服务 php搭建 ...

  8. NSDictionary基本概念

    1.NSDictionar基本概念 什么是NSDictionary NSDictionary翻译过来叫做"字典" 日常生活中,"字典"的作用:通过一个拼音或者汉 ...

  9. Nodejs允许跨域访问

    状况:本地的前端项目(uni-app)以及后台管理(vue-mongo-node)和本地mongo数据库 前台项目端口是8082,后台数据接口是8081. 跨域解决,直接上代码: uni-app的ma ...

  10. Spark入门案例 - 统计单词个数 / wordcount

    Scala版 import org.apache.spark.{SparkConf, SparkContext} object WordCountScala { def main(args: Arra ...