3

这是基于我在调度表上的另一篇文章中提出的切线替代方案。

在objective-c 面向对象设计中,假设我正在设计一个需要外部数据来执行的类。该类有许多变体,每个变体根据不同的外部数据在不同的时间以不同的方式执行。执行/实施此设计的最佳方式是什么?

作为一个具体的例子,假设该类代表一种医疗状况。有 100-200 种不同的医疗条件,每一种都会对不同的刺激作出反应,每一种一旦被触发就会做不同的事情。

我需要帮助的主要困难是如何传递每个变体在触发后执行所需的数据——无法封装的数据,以及不能简单地用作参数以输入到重写函数的数据因医疗状况的每一种变化而有所不同。

例如,当晒伤状况触发时,它需要传入的天气数据来确定严重程度。当失眠触发时,它需要最近服用其他药物的历史。消化不良需要消耗食物的重量。当然,触发点也不同,但我已经枚举了它并将其作为枚举值存储在我的超类中。问题是如何处理数据:我是否为任何子类可能需要的每个可能的输入进行子类化?或者使用像访客这样的设计模式?

谢谢你的帮助。

编辑:这是一个示例情况,我希望可以使我的问题更加具体:

一群病人被送进一个房间,每个人都有不同的 MedicalCondition(在我当前的代码中,我没有继承 MedicalCondition,我只称他们为不同的 NSString 名称)。这是患者的财产。然后,通过程序流程,对每个患者进行各种医学测试。MedicalTest 具有“int power”和“BOOL putsPatientToSleep”和“float chanceOfCausingFainting”等数据,并且从 MedicalTest 到 MedicalTest 也有很大差异。房间本身也具有 isSunny、isDark、fullOfContagiousPatients 等特性,这些特性成为我在 MedicalCondition 爆发时需要的数据。

现在,通过一系列测试,假设发生了一个事件,它是被测患者医疗状况的触发因素之一。假设它被称为 @"sunburn" MedicalTest 具有导致被测试患者的 MedicalCondition 激活的质量。从本质上讲,我的问题是,我在哪里以及如何编写 ExecuteWhenThisConditionFlaresUp(...输入从 MedicalCondition 到 MedicalCondition...) 的激活代码?例如,当晒伤患者的 MedicalCondition 着火时,我如何改变这种反应与寒战患者的反应?给定输入数据,我可以相当简单地编写函数,但问题是输入数据(即函数签名)会因条件而异,而且我还有 100 多个医疗条件做不同的事情,所以我不确定如果它' 将 MedicalCondition 子类化是正确的,只是为了在突发事件中具有不同的行为。有没有办法在objective-c中索引或存储函子?也许编写 100 个不同的函数,所有函数都具有不同的签名,但可以访问通用输入 NSArray,然后使用适当的 NSString 存储适当的函子?(只保留一个 MedicalCondition 类)

4

2 回答 2

3

这听起来像是组合与继承问题的完美示例。

如果您尝试为此使用继承,您将遇到问题。想象一下,您有一个Employee类,其子类TechnicianManager。现在想象你有一个类Customer,有各种子类。当您有一个同时也是经理的客户时会发生什么?在 Objective-C 中有一个单一的继承链(幸运的是),所以这很难用继承建模。

解决这个问题的方法是改用组合。这看似简单 - 它只是意味着您创建一个容器类并添加特征(即协议或具体协作者)。

它会是这样的:

  • 创建顶级MedicalCondition类。
  • 进入这种医疗条件可以进入其他对象。例如慢性病急性病症状、生命体征等。
  • 查看共性并尝试提取可以添加到医疗状况的抽象基类或接口。

听起来好像您可以将Trigger对象的集合添加到MedicalCondition。这可能是协议或抽象基类。

您可能对探索领域驱动设计感兴趣。

至于可能相关的其他特定模式,鉴于您在问题中描述的高级要求,很难说。我想说的是尝试关注需求本身和对其建模的过程,而不是寻找早期应用的模式。当您继续建模时,适当的模式会大声喊出您。

更新:(基于评论讨论)

考虑使用观察者模式。在这你可以有触发器。每当您在触发器上调用 setActivated 方法时,任何观察到此情况的疾病都可以执行操作。Apple 的框架是键值观察 (KVO)。. 还有一个名为 ReactiveCocoa 的开源框架,您可能会觉得它很有趣。

于 2013-11-02T04:18:40.240 回答
2

我能看到的最简单的方法是让每种医疗状况都保留一系列所需的输入和刺激来检查,

@interface Condition <NSObject>
   @property (retain) NSArray* stimuliForCondition
   @property (retain) NSArray* requiredInputsOnceTriggered

   -(BOOL) conditionIsActivated: (NSDictionary*) stimuli;
   -(void) applyConditionWithInputs: (NSDictionary*) inputs;
@end

然后管理每个条件执行的对象可以读取数组,这些数组可能包含与“世界”对象中字典中的键相对应的字符串,其中包含药物历史和食物重量等数据,我称之为“世界数据。”

@interface WorldData
{

 //things are stored as @"Other Medication Taken" -> some value of medication taken
  NSDictionary* allData;
}
@end 

@implemetation PatientData

-(void) checkForCondition 
{
   for (NSObject<Condition>* condition in allConditions) {

       NSMutableDictionary* stimuliDict = [NSMutableDictionary alloc] init];
       for (NSString* keyForStimuli in condition.stimuliForCondition) {

            stimuliDict[keyForStimuli] = allData[keyForStimuli];
       }

       if([condition conditionIsActivated: stimuliDict]) {

             NSMutableDictionary* inputDict = [NSMutableDictionary alloc] init];
             for(NSString* keyForInput in condition.inputs) {

                  inputDict[keyForInput] = allData[keyForInput];
             }

             [condition applyConditionWithInputs: inputDict];
       }
   }
}

这样,您可以将任意数据传递给 Condition 协议中的两个方法,并根据每个条件的要求实现逻辑。

编辑:对于您在评论中提出的问题,我认为这取决于您在这些方法中所需的逻辑的复杂程度。这里有两种情况:

  1. 您只需要检查某组数字是否在某个范围内,或者其他极其通用的检查,并且条件效果的应用同样是通用的。这是这种情况的一个示例,其中测试是否满足刺激仅仅是它是否超过了某个阈值,我们将从 .plist 文件中加载我们的条件,该文件包含一个字典,将阈值映射到刺激名称。

    @interface 条件 ()

    //maps the thresholds for activation to each stimuli
    NSDictionary* stimuliActivations;
    @end
    @implementation Condition
    
    -(id) initWithConditionName: (NSString*) conditionName
    {
    
    if (self = [super init]) {
    
        //load in our stimuli from a plist
        NSData* conditionFileData = [[NSData alloc] initWithContentsOfFile: [[NSBundle mainBundle] pathForResource: conditionName ofType: @"plist" inDirectory: nil]];
    _stimuliForCondition = [[NSPropertyListSerialization propertyListFromData: conditionFileData mutabilityOption: NSPropertyListImmutable format: NULL errorDescription: nil] retain];
    
        _requiredInputsOnceTriggered = //the same thing for the appropriate plist file
    
        stimuliActivations = // same thing
    }
    
         return self;
    }
    
    -(BOOL) conditionIsActivated: (NSDictionary*) stimuli
    {
    
        NSUInteger numActivatedStimuli = 0;
        for (NSString* currStimuli in [stimuli allkeys]) {
            if (stimuli[currStimuli] > stimuliActivations[currStimuli]) {
    
               numActivatedStimuli++;
            }
        }
    
        return (numActivatedStimuli == _stimuliForCondition.length ? YES : NO);
     }
    
    @end
    
  2. 案例二:刺激和/或条件的应用很复杂,需要为每个条件定制逻辑,不能将其分解为简单的通用实现,例如“如果值大于”、“值介于”、“值等于”。在这种情况下,您应该创建一个由其他对象实现的“条件”协议(因此您不必继承一些您永远不想实例化的通用类),并为每个条件分别实现逻辑,使用该接口确保您只需要一个方法签名。这样你就不会有臃肿的 Condition.m。

我认为为此使用选择器太过分了。当您需要确定在运行时发送的方法时,选择器和调用是很好的选择,但是这里有许多选项比这更简单并且更好地映射到现实(疾病是对象,而不是函数)。

于 2013-11-02T04:36:20.133 回答