2015年9月2日星期三

设计模式之:适配器模式 - 雪山飞猪

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
设计模式之:适配器模式 - 雪山飞猪  阅读原文»

适配器很容易理解, 大多数人家庭都有手机转接器, 用来为移动电话充电,这就是一种适配器. 如果只有USB接头, 就无法将移动电话插到标准插座上. 实际上, 必须使用一个适配器, 一端接USB插头, 一端接插座. 当然, 你可以拿出电气工具,改装USB连接头, 或者重新安装插座, 不过这样会带来很多额外的工作, 而且可能会把连接头或插座弄坏. 所以, 最可取的方法就是找一个适配器. 软件开发也是如此.

类适配器模式(使用继承)

类适配器模式很简单, 不过与对象适配器模式相比, 类适配器模式的灵活性弱些, 类适配器简单的原因在于 , 适配器(Adapter)会从被适配者(Adaptee)继承功能, 所以适配模式中需要编写的代码比较少.
由于类适配器模式包含双重继承, 但是PHP并不支持双重继承, 不过幸运的是,PHP可以用接口来模拟双重继承, 下面是一个正确的结构, 不仅继承了一个类, 同时还继承了一个接口
class ChildClass extends ParentClass implements ISomeAdapter
{

}
实现类适配器模式时, 参与者必须包括一个PHP接口
下面以一个货币兑换为例来演示:
假设有一个企业网站在同时销售软件服务和软件产品, 目前, 所有交易都在美国进行, 所以完全可以用美元来完成所有计算.现在开发人员希望能有一个转换器能处理美元和欧元的兑换, 而不改变原来按美元交易额的类.通过增加一个适配器, 现在程序即可以用美元计算也可以用欧元计算.
DollarCalc.php
<?php
class DollarCalc
{
private $dollar;
private $product;
private $service;
public $rate = 1;
public function requestCalc($productNow, $serviceNow)
{
$this->product = $productNow;
$this->service = $serviceNow;
$this->dollar = $this->product + $this->service;
return $this->requestTotal();
}
public function requestTotal()
{
$this->dollar *= $this->rate;
return $this->dollar;
}
}
查看这个类,可以看到其中有一个属性$rate,requestTotal()方法使用$rate计算一次交易的金额.在这个版本中, 这个值设置为1,实际上总金额无需再乖以兑换率, 不过如果要为客户提供折扣或者要增加额外服务或产品的附加费, $rate变量会很方便. 这个类并不是适合器模式的一部分, 不过这是一个起点.
需求变化了
现在客户的公司要向欧洲发展,所以需要开发一个应用, 能够用欧元完成同样的计算. 你希望这个欧元计算能够像DollarCalc一样, 所要做的就是改变变量名.
EuroCalc.php
<?php
class EuroCalc
{
private $euro;
private $product;
private $service;
public $rate = 1;
public function requestCalc($productNow, $serviceNow)
{
$this->product = $productNow;
$this->service = $serviceNow;
$this->euro = $this->product + $this->service;
return $this->requestTotal();
}
public function requestTotal()
{
$this->euro *= $this->rate;
return $this->euro;
}
}
接下来, 再把应用的其余部分插入到EuroCalc类中. 不过,因为客户的所有数据都是按美元计算的.换句话说, 如果不重新开发整个程序, 就无法在系统中"插入"这个欧元计算. 但是你不想这么做. 为了加入EuroCalc, 你需要一个适配器: 就像找一个适配器来适应欧洲的插座一样, 可以创建一个适配器, 使你的系统能够使用欧元. 幸运的是, 类适配器正是为这样的情况设计的.首先需要创建一个接口. 在这个类图中, 这个接口名为ITarget. 它只有一个方法requester(). requester()是一个抽象方法, 要由接口的具体实现来实现这个方法.
ITarget.php
<?php
interface ITarget
{
public function requester();
}
现在开发人员可以实现requester()方法, 请求欧元而不是美元.
在使用继承的适配器设计模式中, 适配器(Adapter)参与都既实现ITarget接口,还实现了具体类EuroCalc. 创建EuroAdapter不需要做太多工作, 因为大部分工作已经在EuroCal类中完成.现在要做的就是实现request()方法, 使它能把美元值转换为欧元值.
EuroAdapter.php
<?php
include_once('EuroCalc.php');
include_once('ITarget.php');
class EuroAdapter extends EuroCalc implements ITarget
{
public function __construct()
{
$this->requester();
}
public function requester()
{
$this->rate = 0.8111;
return $this->rate;
}
}
类适配模式中, 一个具体类会继承另一个具体类, 有这种结构的设计模式很少见, 大多数设计模式中, 几乎都是继承一个抽象类, 并由类根据需要实现其抽象方法和属性. 换句话说, 一般谈到继承时, 都是具体类继承抽象类.
由于既实现了一个接口又扩展了一个类, 所以EuroAdapter类同时拥有该接口和具体类的接口. 通过使用requester()方法, EuroAdapter类可以设置rate值(兑换率), 从而能使用被适配者的功能, 而元而做任何改变.
下面定义一个Client类, 从EuroAdapter和DollarCalc类发出请求. 可以看到,原来的DollarCalc仍能很好地工作, 不过它没有ITarget接口.
Client.php
<?php
include_once('EuroAdapter.php');
include_once('DollarCalc.php');
class Client
{
public function __construct()
{
$euro = '€';
echo "区元: $euro" . $this->makeApapterRequest(new EuroAdapter()) . '<br />';
echo "美元: $: " . $this->makeDollarRequest(new DollarCalc()) . '<br />';
}
private function makeApapterRequest(ITarget $req)
{
return $req->requestCalc(40,50);
}
private function makeDollarRequest(DollarCalc $req)
{
return $req->requestCalc(40,50);
}
}
$woker = new Client();
运行结果如下:
Euros: €72.999
Dollars: $: 90
可以看到,美元和欧元都可以处理, 这就是适配器模式的方便之处.
这个计算很简单, 如果是针对更为复杂的计算, 继承要提供建立类适配器的Target接口的必要接口和具体实现

使用组合的适配器模式

对象适配器模式使用组合而不是继承, 不过它也会完成同样的目标. 通过比较这两个版本的适配器模式, 可以看出它们各自的优缺点. 采用类适配器模式时,适配器可以继承它需要的大多数功能, 只是通过接口稍微调. 在对象适配器模式中 适配器(Adapter)参与使用被适配者(Adaptee), 并实现Target接口. 在类适配器模式中, 适配器(Adapter)则是一个被适配者(Adaptee), 并实现Target接口.
示例: 从桌面环境转向移动环境
PHP程序员经常会遇到这样一个问题:需要适应移动环境而做出调整.不久之前,你可能只需要考虑提供一个网站来适应多种不同的桌面环境. 大多数桌面都使用一个布局, 再由设计人员让它更美观. 对于移动设备, 设计人员和开发人员不仅需要重新考虑桌面和移动环境中页面显示的设计元素, 还要考虑如何从一个环境切换到另一个环境.
首先来看桌面端的类Desktop(它将需要一个适配器). 这个类使用了一个简单但很宽松的接口:
IFormat.php
<?php
interface IFormat
{
public function formatCSS();
public function formatGraphics();
public function horizontalLayout();
}
它支持css和图片选择, 不过其中一个方法指示一种水平布局, 我们知道这种布局并不适用小的移动设备.下面给出实现这个接口的Desktop类
Desktop.php
<?php
include_once('IFormat.php');
class Desktop implements IFormat
{
public function formatCSS()
{
echo "引用desktop.css<br />";
}
public function formatGraphics()
{
echo "引用desktop.png图片<br />";
}
public function horizontalLayout()
{
echo '桌面:水平布局';
}
}
问题来了, 这个布局对于小的移动设备来说太宽了. 所以我们的目标是仍采用同样的内容, 但调整为一种移动设计.
下面来看移动端的类Mobile
首先移动端有一个移动端的接口
IMobileFormat
<?php
interface IMobileFormat
{
public function formatCSS();
public function formatGraphics();
public function verticalLayout();
}
可以看到, IMobileFormat接口和IFormat接口是不一样的,也就是不兼容的, 一个包含了方法horizontalLayout(), 另一个包含方法verticalLaout(), 它们的差别很小, 最主要的区别是: 桌面设计可以采用水平的多栏布局, 而移动设计要使用垂直布局,而适配器就是要解决这个问题
下面给出一个实现了IMoibleFormat接口的Mobile类
Mobile.php
<?php
include_once('IMobileFormat.php');
class Mobile implements IMobileFormat
{
public function formatCSS()
{
echo "引用mobile.css<br />";
}
public function formatGraphics()
{
echo "引用mobile.png图片<br />";
}
public function verticalLayout()
{
echo '移动端:垂直布局
C# Excel 为图表添加趋势线、误差线 - Yesi  阅读原文»

Excel图表能够将数据可视化,在图表中另行添加趋势线和误差线,可对数据进行进一步的数据分析和统计的可视化处理。Excel中的趋势线可用于趋势预测/回归分析,共6中类型:指数(X),线性(L),对数(0),多项式(P),幂(W),移动平均(M)。误差线可用于显示潜在的误差或相对于系列中每个数据标志的不确定程度。Excel中可设置误差线的显示方向:正负偏差,负偏差,正偏差;以及设置误差类型及误差量:固定值,百分比,标准偏差,标准误差,自定义类型。

本篇文章主要介绍,使用免费版的Free Spire.XLSC#中独立创建Excel文档,生成折线图、柱状图,并添加趋势线和误差线。

需添加的命名空间

using Spire.Xls;
using System.Drawing;

步骤详解:
步骤一:独立创建Excel文件和表单。

Workbook workbook = new Workbook();
workbook.CreateEmptySheets(1);
Worksheet sheet = workbook.Worksheets[0];

步骤二:为Excel单元格添加示例数据。

sheet.Name = "误差线和趋势线演示";
sheet.Range["A1"].Value = "月份";
sheet.Range["A2"].Value = "一月";
sheet.Range["A3"].Value = "二月";
sheet.Range["A4"].Value = "三月";
sheet.Range["A5"].Value = "四月";
sheet.Range["A6"].Value = "五月";
sheet.Range["A7"].Value = "六月";
sheet.Range["B1"].Value = "计划量";
sheet.Range["B2"].NumberValue = 3.3;
sheet.Range["B3"].NumberValue = 2.5;
sheet.Range["B4"].NumberValue = 2.0;
sheet.Range["B5"].NumberValue = 3.7;
sheet.Range["B6"].NumberValue = 4.5;
sheet.Range["B7"].NumberValue = 4.0;
sheet.Range["C1"].Value = "实际量";
sheet.Range["C2"].NumberValue = 3.8;
sheet.Range["C3"].NumberValue = 3.2;
sheet.Range["C4"].NumberValue = 1.7;
sheet.Range["C5"].NumberValue = 3.5;
sheet.Range["C6"].NumberValue = 4.5;
sheet.Range["C7"].NumberValue = 4.3;

步骤三:生成折线图,为其添加趋势线和误差线。

//生成折线图,设置位置
Chart chart = sheet.Charts.Add(ExcelChartType.Line);
chart.DataRange = sheet.Range["B1:B7"];
chart.SeriesDataFromRange = false;
chart.TopRow = 6;
chart.BottomRow = 25;
chart.LeftColumn = 2;
chart.RightColumn = 9;
chart.ChartTitle = "百分比正偏差误差线和对数趋势线示例";
chart.ChartTitleArea.IsBold = true;
chart.ChartTitleArea.Size = 12;
Spire.Xls.Charts.ChartSerie cs1 = chart.Series[0];
cs1.CategoryLabels = sheet.Range["A2:A7"];
//添加对数趋势线
cs1.TrendLines.Add(TrendLineType.Logarithmic);
//添加10%正偏差误差线
cs1.ErrorBar(true, ErrorBarIncludeType.Plus, ErrorBarType.Percentage,10);

步骤四:生成柱状图,并为其添加趋势线和误差线。

//生成柱状图作为对照组
Chart chart2 = sheet.Charts.Add(ExcelChartType.ColumnClustered);
chart2.DataRange = sheet.Range["B1:C7"];
chart2.SeriesDataFromRange = false;
chart2

阅读更多内容

没有评论:

发表评论