在对象之间搬移特性(Moving features Between Objects)

本文介绍了软件重构中的八项关键技巧,包括搬移函数、搬移值域、提炼类等,帮助程序员更好地组织代码,提高软件质量和维护性。
 

在对象的设计过程中,[决定把责任放在哪儿]即使不是最重要的事,也是最重要的事之一。

1 Move Method(搬移函数)

概述

你的程序中,有个函数与其所驻class之外的另一个class进行更多交流:调用后者,或被后者调用。在该函数最常引用(指涉)的class中建立一个有着类似行为的新函数,将旧函数变成一个单纯的委托函数(delegating method),或是将旧函数完全移除;

动机

[函数搬移]是重构理论的支柱。

如果一个class有太多行为,或如果一个class与另一个class有太多合作而形成高度耦合(highly coupled),就应该搬移函数;

是否应该移动一个函数,这往往不是一个容易做出的决定,如果不能肯定是否应该移动一个函数,那么或许[移动这个函数与否]并不是那么重要。

范例

我们用一个表示[账户]的class来说明这项重构:

ContractedBlock.gifExpandedBlockStart.gifCode
 1 class Account
 2 {
 3     private AccountType _type;
 4     private int _daysOverdrawn;
 5 
 6     double overdraftCharge() //透支金计费
 7     {
 8         if (_type.isPremium())
 9         {
10             double result = 10;
11             if (_daysOverdrawn > 7)
12             {
13                 result += (_daysOverdrawn - 7* 0.85;
14             }
15             return result;
16         }
17         else
18         {
19             return _daysOverdrawn * 1.75;
20         }
21     }
22 
23     double bankCharge()
24     {
25         double result = 4.5;
26         if (_daysOverdrawn > 0)
27             result += overdraftCharge();
28         return result;
29     }
30 }
31 
32 class AccountType
33 {
34     public bool isPremium()
35     {
36         return true;
37     }
38 }


假设有数种账户,每一种都有自己的[透支金计费规则]。所以透支金计费方法我们不应该放在账户里,我们希望能够把透支金计费方法overdraftCharge()放到AccountType类中。

ContractedBlock.gifExpandedBlockStart.gifCode
 1 class Account
 2 {
 3     private AccountType _type;
 4     private int _daysOverdrawn;
 5 
 6     double bankCharge()
 7     {
 8         double result = 4.5;
 9         if (_daysOverdrawn > 0)
10             result += _type.overdraftCharge(_daysOverdrawn);
11         return result;
12     }
13 }
14 class AccountType
15 {
16     public double overdraftCharge(int daysOverdrawn) //透支金计费
17     {
18         if (isPremium())
19         {
20             double result = 10;
21             if (daysOverdrawn > 7)
22             {
23                 result += (daysOverdrawn - 7* 0.85;
24             }
25             return result;
26         }
27         else
28         {
29             return daysOverdrawn * 1.75;
30         }
31     }
32 
33     public bool isPremium()
34     {
35         return true;
36     }
37 }


上面,我们把透支金计费方法移到了账户类型AccountType类中,这样对于不同的账户透支金计费规则,可以使用多态来实现它的计费,而账户类只需要一个接口就可以。

另外,对此例中被移函数,只取用(指涉)了一个值域(_daysOverdrawn),所以我只需要将这个值域作为参数传给target method就可以了。如果被移函数调用了Account中的另一个函数的话,就不能这么简单的处理了,这种情况我必须将source object传递给teget method。

如果target method需要太多source object特性,就得进一步重构。通常这种情况下应该分解target method,并将其中一部分移回source class;

备注

target method需要使用source class特性的时候,有四种选择:

1 将这个特性也移到target class

2 建立或使用一个从target classsource class的引用;

3 source class当作参数传给target method

4 如果所需特性是个变量,将它作为参数传给target method

 

很明显,上述用的是第四种。具体用哪种看实际情况。

2 Move Field(搬移值域)

概述

你的程序中,某个field被其所驻class之外的另一个class更多地用到。那么在target class中建立一个new field,修改source field的所有用户,令它们必胜new field

动机

classes之间移动状态(states)和行为,是重构过程中必不可少的措施。如果对于一个field(值域),在其所驻class之外的另一个class中有更多函数使用了它,就可以考虑搬移这个field。所谓[使用]可能是通过设值/取值(setting/getting)函数间接进行。

范例

下面是Account class的部分代码:

ContractedBlock.gifExpandedBlockStart.gifCode
class Account
{
    
private AccountType _type;
    
private double _interestRate;

    
double interestForAmountDays(double amount, int days)
    {
        
return _interestRate * amount * days / 365;
    }
}
class AccountType
{
}

 

每种账户类型的利率不同,所以我想把表示利率的_interestRate搬移到AccountType中去。

ContractedBlock.gifExpandedBlockStart.gifCode
class Account
{
    
private AccountType _type;

    
double interestForAmountDays(double amount, int days)
    {
        
return _type.InterestRate * amount * days / 365;
    }
}
class AccountType
{
    
private double _interestRate;

    
public double InterestRate
    {
        
get { return _interestRate; }
        
set { _interestRate = value; }
    }
}

 

3 Extract Class(提炼类)

概述

某个class做了应该由两个classes做的事,建立一个新class,将相关的值域和函数从旧class搬移到新class

动机

一个class应该是一个清楚的抽象(abstract),处理一些明确的责任。

 

在实际工作中,class会不断成长扩展。你会在这加入一些功能,在那加入一些数据,给某个class添加一项新责任时,你会觉得不值得为这项责任分离出一个单独的class。于是,随着责任的不断增加,这个class会变得过份复杂。

 

如果某些数据和某些函数总是一起出现,如果某些数据经常同时变化甚至彼此相依,这就表示你应该将它们分离出去。

范例

从一个简单的Person class开始:

ContractedBlock.gifExpandedBlockStart.gifCode
class Person
{
    
private string name;

    
public string Name
    {
        
get { return name; }
    }

    
private string officeAreaCode;

    
public string OfficeAreaCode
    {
        
get { return officeAreaCode; }
        
set { officeAreaCode = value; }
    }

    
private string officeNumber;

    
public string OfficeNumber
    {
        
get { return officeNumber; }
        
set { officeNumber = value; }
    }

    
public string GetTelephoneNumber()
    {
        
return ("(" + officeAreaCode + ")" + officeNumber);
    }
}

 

这上面例子中,我们可以将[与电话号码相关]的行为分离到一个独立class中,如下所示:

ContractedBlock.gifExpandedBlockStart.gifCode
class Person
{
    
private TelephoneNumber officeTelephone = new TelephoneNumber();

    
private string name;

    
public string Name
    {
        
get { return name; }
    }

    
public string GetTelephoneNumber()
    {
        
return officeTelephone.GetTelephoneNumber();
    }

}
class TelephoneNumber
{
    
public string GetTelephoneNumber()
    {
        
return ("(" + officeAreaCode + ")" + officeNumber);
    }

    
private string officeAreaCode;

    
public string OfficeAreaCode
    {
        
get { return officeAreaCode; }
        
set { officeAreaCode = value; }
    }

    
private string officeNumber;

    
public string OfficeNumber
    {
        
get { return officeNumber; }
        
set { officeNumber = value; }
    }
}

 

4 Inline Class(将类内联化)

概述

正好与Extract class相反,如果你的某个class没有做太多事情(没有承担足够责任),就可以将class的所有特性搬移到另一个class中,然后移除原class

动机

如果一个class不再承担足够的责任、不再有单独存在的理由,就应该挑选这一[萎缩class]的最频繁的用户(也是个class),以Inline Class手法将[萎缩class]塞进去。

范例

Extract Class范例所示,可以将TelephoneNumber塞回到Person类去。

5 Hide Delegate(隐藏[委托关系])

概述

对于客户直接调用其server object(服务对象)的delegate class,在server端(某个class)建立客户所需的所有函数,用以隐藏委托关系(delegation)

动机

[封装]即使不是对象的最关键特征,也是最关键特征之一。[封装]意味着每个对象都应该尽可能少了解系统的其他部分。如此一来,一旦发生了变化,需要了解这一变化的对象就会比较少,这会使得变化比较容易进行。

 

如果某个客户调用了[建立于server object的某个值域基础之上]的函数,那么客户就必须知道这一委托对象(即server object的特殊值域)。万一委托关系发生变化,客户也得相应变化。你可以在server端放置一个简单的委托函数(delegate method),将委托关系隐藏起来,从而去除这种依存性。这么一来即使将来发生委托关系的变化,变化将被限制在server中,不会波及客户。

范例

我们从代表[人]的Person类和代表部门的Department类来看:

ContractedBlock.gifExpandedBlockStart.gifCode
class Person
{
    
private Department department;

    
public Department Department
    {
        
get { return department; }
        
set { department = value; }
    }
}

class Department
{
    
private string chargeCode;
    
private Person manager;

    
public Department(Person manager)
    {
        
this.manager = manager;
    }

    
public Person Manager
    {
        
get { return manager; }
    }
}

 

如果客户希望知道某人的经理是谁,他必须先取得这个人所属的部门Department对象:

manager = onePerson.Department.Manager;

这样的代码就对客户揭露了Department的工作原理,于是客户知道:Department用以追踪[经理]这条信息。我们可以在Person中建立一个简单的委托函数,对客户隐藏Department。

    public Person GetManager()

    {

        return department.Manager;

}

这样,客户希望知道某人的经理是谁,就可以这样调用:

manaer = onePerson.GetManager();

只要完成了对Department所有函数的委托关系,并相应修改了Person的所有客户,就可以移除Person中的Department访问属性。

6 Remove Middle Man(移除中间人)

概述

某个class做了过多的简单委托动作(simple delegation),让客户直接调用delegate(委托类)

动机

Hide Delegate中,我们隐藏了delegate object,这层封装虽然使得客户只关心server object类,但同时它也付出了一定代价:每当客户要使用delegate(受托类)的新特性时,你就必须在server端添加一个简单委托函数。随着delegate的特性(功能)越来越多,这一过程会让你痛苦不已。Server完全变成了一个[中间人],此时你就应该让客户直接调用delegate

范例

Hide Delegate中范例所示,做一个反向过程。

备注

什么程序的隐藏才是合适的,这很难说,你可以在系统运行过程中不断进行调整,随着系统的变化,[合适的隐藏度]这个尺度也相应改变。

7 Introduce Foreign Method(引入外加函数)

概述

你所使用的server class需要一个额外函数,但你无法修改这个class;这时可以在client class中建立一个函数,并以一个server class实体作为第一引数(argument)。

动机

你使用一个免费dll,它真得不错,为你提供了你想要的所有服务,而后,你又需要一个新服务,这个class却无法供应,如果这个dll是开源的,你有源码,那么你可以自行添加一个你想要的新函数,如果不是开源的,你就得在客户端编码,补足你要的那个函数。

范例

假如我需要跨过一个收费周期(billing period),原本代码像这样:

Date newStart = new Date(previousEnd.getYear(),

previousEnd.getMonth(),previousEnd.getDate() + 1);

我可以通过添加一个外加函数:

Date newStart = nextDay(previousEnd);

private Date nextDay(Date arg)

{

    return new Date(arg.getYear(),arg.getMonth(),arg.getDate() + 1);

}

备注

外加函数终归是权宜之计,如果有可能,仍然应该将这些函数搬移到它们的理想家园。

8 Introduce Local Extension(引入本地扩展)

概述

你所使用的server class需要一些额外函数,但你无法修改这个class

建立一个新class,使它包含这些额外函数,让这个扩展品成为source classsubclass(子类)或wrapper(外覆类)。

动机

对于Introduce Foreign Method来说,如果你需要的外加函数超过两个,就很难控制住它们了,所以,你需要将这些函数组织在一起,放到一个恰当地方去。

所谓local extension,是一个独立的class,但也是其extended classsubtype(不同于subclass,它和extended class并不一定存在严格的继承关系,只要能够提供extended class的所有特性即可)。

范例:使用Subclass(子类)

public class MyDateSub : Date
{
    
private Date nextDay()
    {
        
return new Date(getYear(),getMonth(),getDate() + 1);
    }
}

 

范例:使用Wrapper(外覆类)

ContractedBlock.gifExpandedBlockStart.gifCode
public class MyDateWrap
{
    
private Date original;

    
public MyDateWrap(Date arg)
    {
        original 
= arg;
    }

    
public int getYear()
    {
        
return original.getYear();
    }
    
public int getMonth()
    {
        
return original.getMonth();
    }
    
public int getDate()
    {
        
return original.getDate();
    }
    
private Date nextDay()
    {
        
return new Date(getYear(),getMonth(),getDate() + 1);
    }
}

 

转载于:https://www.cnblogs.com/wych/archive/2009/09/19/1569788.html

一、数据采集层:多源人脸数据获取 该层负责从不同设备 / 渠道采集人脸原始数据,为后续模型训练与识别提供基础样本,核心功能包括: 1. 多设备适配采集 实摄像头采集: 调用计算机内置摄像头(或外接 USB 摄像头),通过OpenCV的VideoCapture接口实捕获视频流,支持手动触发 “拍照”(按指定快捷键如Space)或自动定采集(如每 2 秒采集 1 张),采集自动框选人脸区域(通过Haar级联分类器初步定位),确保样本聚焦人脸。 支持采集参数配置:可设置采集分辨率(如 640×480、1280×720)、图像格式(JPG/PNG)、单用户采集数量(如默认采集 20 张,确保样本多样性),采集过程中实显示 “已采集数量 / 目标数量”,避免样本不足。 本地图像 / 视频导入: 支持批量导入本地人脸图像文件(支持 JPG、PNG、BMP 格式),自动过滤非图像文件;导入视频文件(MP4、AVI 格式),可按 “固定帧间隔”(如每 10 帧提取 1 张图像)或 “手动选择帧” 提取人脸样本,适用于无实摄像头场景。 数据集对接: 支持接入公开人脸数据集(如 LFW、ORL),通过预设脚本自动读取数据集目录结构(按 “用户 ID - 样本图像” 分类),快速构建训练样本库,无需手动采集,降低系统开发与测试成本。 2. 采集过程辅助功能 人脸有效性校验:采集通过OpenCV的Haar级联分类器(或MTCNN轻量级模型)实检测图像中是否包含人脸,若未检测到人脸(如遮挡、侧脸角度过大),则弹窗提示 “未识别到人脸,请调整姿态”,避免无效样本存入。 样本标签管理:采集需为每个样本绑定 “用户标签”(如姓名、ID 号),支持手动输入标签或从 Excel 名单批量导入标签(按 “标签 - 采集数量” 对应),采集完成后自动按 “标签 - 序号” 命名文件(如 “张三
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值