这些准则使得开发出易扩展、可维护的软件变得更简略。也使得代码更精简、易于重构。同样也是灵敏开发和自适应软件开发的一部分。
备注:这不是一篇简略的介绍”欢迎来到_S.O.L.I.D”的文章,这篇文章想要说明S.O.L.I.D是什么。(相关教程推荐:php视频教程)
S.O.L.I.D意思是:
扩展出来的首字母缩略词看起来可能很复杂,实际上它们很简略了解。
S-单一功能准则
O-开闭准则
L-里氏替换准则
I-接口阻隔准则
D-依靠回转准则
接下来让咱们看看每个准则,来了解为什么S.O.L.I.D能够协助咱们成为更好的开发人员。
单一责任准则
缩写是S.R.P,该准则内容是:
一个类有且只能有一个要素使其改动,意思是一个类只应该有单一责任.
例如,假设咱们有一些图形,而且想要核算这些图形的总面积.是的,这很简略对不对?
classCircle{
public$radius;
publicfunctionconstruct($radius){
$this->radius=$radius;
}
}
classSquare{
public$length;
publicfunctionconstruct($length){
$this->length=$length;
}
}
首要,咱们创立图形类,该类的结构办法初始化必要的参数.接下来,创立AreaCalculator类,然后编写核算指定图形总面积的逻辑代码.
classAreaCalculator{
protected$shapes;
publicfunction__construct($shapes=array()){
$this->shapes=$shapes;
}
publicfunctionsum(){
//logictosumtheareas
}
publicfunctionoutput(){
returnimplode(”,array(
“”,
“Sumoftheareasofprovidedshapes:”,
$this->sum(),
“”
));
}
}
AreaCalculator运用办法,咱们只需简略的实例化这个类,而且传递一个图形数组,在页面底部展示输出内容.
$shapes=array(
newCircle(2),
newSquare(5),
newSquare(6)
);
$areas=newAreaCalculator($shapes);
echo$areas->output();
输出办法的问题在于,AreaCalculator处理了数据输出逻辑.因而,假如用户希望将数据以json或许其他格局输出呢?
一切逻辑都由AreaCalculator类处理,这恰恰违反了单一责任准则(SRP);AreaCalculator类应该只担任核算图形的总面积,它不应该关怀用户是想要json仍是HTML格局数据。
因而,要处理这个问题,能够创立一个SumCalculatorOutputter类,并运用它来处理所需的显现逻辑,以处理一切图形的总面积该如何显现。
SumCalculatorOutputter类的工作办法如下:
$shapes=array(
newCircle(2),
newSquare(5),
newSquare(6)
);
$areas=newAreaCalculator($shapes);
$output=newSumCalculatorOutputter($areas);
echo$output->JSON();
echo$output->HAML();
echo$output->HTML();
echo$output->JADE();
现在,无论你想向用户输出什么格局数据,都由SumCalculatorOutputter类处理。
开闭准则
目标和实体应该对扩展敞开,可是对修正封闭.
简略的说便是,一个类应该不用修正其本身就能很简略扩展其功能.让咱们看一下AreaCalculator类,特别是sum办法.
publicfunctionsum(){
foreach($this->shapesas$shape){
if(is_a($shape,’Square’)){
$area[]=pow($shape->length,2);
}elseif(is_a($shape,’Circle’)){
$area[]=pi()*pow($shape->radius,2);
}
}
returnarray_sum($area);
}
假如咱们想用sum办法能核算更多图形的面积,咱们就不得不增加更多的if/elseblocks,然而这违反了开闭准则.
让这个sum办法变得更好的办法是将核算每个形状面积的代码逻辑移出sum办法,将其放进各个形状类中:
classSquare{
public$length;
publicfunction__construct($length){
$this->length=$length;
}
publicfunctionarea(){
returnpow($this->length,2);
}
}
相同的操作应该被用来处理Circle类,在类中增加一个area办法。现在,核算任何形状面积之和应该像下边这样简略:
publicfunctionsum(){
foreach($this->shapesas$shape){
$area[]=$shape->area();
}
returnarray_sum($area);
}
接下来咱们能够创立另一个形状类并在核算总和时传递它而不损坏咱们的代码。然而现在又出现了另一个问题,咱们怎么能知道传入AreaCalculator的目标实际上是一个形状,或许形状目标中有一个area办法?
接口编码是实践S.O.L.I.D的一部分,例如下面的比方中咱们创立一个接口类,每个形状类都会完成这个接口类:
interfaceShapeInterface{
publicfunctionarea();
}
classCircleimplementsShapeInterface{
public$radius;
publicfunction__construct($radius){
$this->radius=$radius;
}
publicfunctionarea(){
returnpi()*pow($this->radius,2);
}
}
在咱们的AreaCalculator的sum办法中,咱们能够查看供给的形状类的实例是否是ShapeInterface的完成,不然咱们就抛出一个异常:
publicfunctionsum(){
foreach($this->shapesas$shape){
if(is_a($shape,’ShapeInterface’)){
$area[]=$shape->area();
continue;
}
thrownewAreaCalculatorInvalidShapeException;
}
returnarray_sum($area);
}
里氏替换准则
假如对每一个类型为T1的目标o1,都有类型为T2的目标o2,使得以T1界说的一切程序P在一切的目标o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
这句界说的意思是说:每个子类或许衍生类能够毫无问题地替代基类/父类。
依然运用AreaCalculator类,假设咱们有一个VolumeCalculator类,这个类承继了AreaCalculator类:
classVolumeCalculatorextendsAreaCalulator{
publicfunctionconstruct($shapes=array()){
parent::construct($shapes);
}
publicfunctionsum(){
//logictocalculatethevolumesandthenreturnandarrayofoutput
returnarray($summedData);
}
}
SumCalculatorOutputter类:
classSumCalculatorOutputter{
protected$calculator;
publicfunction__constructor(AreaCalculator$calculator){
$this->calculator=$calculator;
}
publicfunctionJSON(){
$data=array(
‘sum’=>$this->calculator->sum();
);
returnjson_encode($data);
}
publicfunctionHTML(){
returnimplode(”,array(
”,
‘Sumoftheareasofprovidedshapes:’,
$this->calculator->sum(),
”
));
}
}
假如咱们运转像这样一个比方:
$areas=newAreaCalculator($shapes);
$volumes=newAreaCalculator($solidShapes);
$output=newSumCalculatorOutputter($areas);
$output2=newSumCalculatorOutputter($volumes);
程序不会出问题,但当咱们运用$output2目标调用HTML办法时,咱们接收到一个E_NOTICE过错,提示咱们数组被当做字符串运用的过错。
为了修复这个问题,只需:
publicfunctionsum(){
//logictocalculatethevolumesandthenreturnandarrayofoutput
return$summedData;
}
而不是让VolumeCalculator类的sum办法返回数组。
$summedData是一个浮点数、双精度浮点数或许整型。
接口阻隔准则
运用方(client)不应该依靠强制完成不运用的接口,或不应该依靠不运用的办法。
继续运用上面的shapes比方,已知拥有一个实心块,假如咱们需要核算形状的体积,咱们能够在ShapeInterface中增加一个办法:
interfaceShapeInterface{
publicfunctionarea();
publicfunctionvolume();
}
任何形状创立的时候有必要完成volume办法,可是【平面】是没有体积的,完成这个接口会强制的让【平面】类去完成一个自己用不到的办法。
ISP准则不允许这么去做,所以咱们应该创立另外一个拥有volume办法的SolidShapeInterface接口去替代这种办法,这样相似立方体的实心体就能够完成这个接口了:
interfaceShapeInterface{
publicfunctionarea();
}
interfaceSolidShapeInterface{
publicfunctionvolume();
}
classCuboidimplementsShapeInterface,SolidShapeInterface{
publicfunctionarea(){
//核算长方体的表面积
}
publicfunctionvolume(){
//核算长方体的体积
}
}
这是一个更好的办法,可是要注意提示类型时不要仅仅提示一个ShapeInterface或SolidShapeInterface。
你能创立其它的接口,比方ManageShapeInterface,并在平面和立方体的类上完成它,这样你能很简略的看到有一个用于办理形状的api。例:
interfaceManageShapeInterface{
publicfunctioncalculate();
}
classSquareimplementsShapeInterface,ManageShapeInterface{
publicfunctionarea(){/Dostuffhere/}
publicfunctioncalculate(){
return$this->area();
}
}
classCuboidimplementsShapeInterface,SolidShapeInterface,ManageShapeInterface{
publicfunctionarea(){/Dostuffhere/}
publicfunctionvolume(){/Dostuffhere/}
publicfunctioncalculate(){
return$this->area()+$this->volume();
}
}
现在在AreaCalculator类中,咱们能够很简略地用calculate替换对area办法的调用,并查看目标是否是ManageShapeInterface的实例,而不是ShapeInterface。
依靠倒置准则
最后,但绝不是最不重要的:
实体有必要依靠笼统而不是具体的完成.即高等级模块不应该依靠低等级模块,他们都应该依靠笼统.
这或许听起来让人头大,可是它很简略了解.这个准则能够很好的解耦,举个比方似乎是解说这个准则最好的办法:
classPasswordReminder{
private$dbConnection;
publicfunction__construct(MySQLConnection$dbConnection){
$this->dbConnection=$dbConnection;
}
}
首要MySQLConnection是低等级模块,然而PasswordReminder是高等级模块,可是根据S.O.L.I.D.中D的解说:依靠于笼统而不依靠与完成,上面的代码段违反了这一准则,由于PasswordReminder类被强制依靠于MySQLConnection类.
稍后,假如你希望修正数据库驱动,你也不得不修正PasswordReminder类,因而就违反了Open-closeprinciple.
此PasswordReminder类不应该重视你的使用运用了什么数据库,为了进一步处理这个问题,咱们「面向接口写代码」,由于高等级和低等级模块都应该依靠于笼统,咱们能够创立一个接口:
interfaceDBConnectionInterface{
publicfunctionconnect();
}
这个接口有一个衔接数据库的办法,MySQLConnection类完成该接口,在PasswordReminder的结构办法中不要直接将类型约束设置为MySQLConnection类,而是设置为接口类,这样无论你的使用运用什么类型的数据库,PasswordReminder类都能毫无问题地衔接数据库,且不违反开闭准则.
classMySQLConnectionimplementsDBConnectionInterface{
publicfunctionconnect(){
return”Databaseconnection”;
}
}
classPasswordReminder{
private$dbConnection;
publicfunction__construct(DBConnectionInterface$dbConnection){
$this->dbConnection=$dbConnection;
}
}
从上面一小段代码,你现在能看出高等级和低等级模块都依靠于笼统了。
总结
说实话,S.O.L.I.D一开始似乎很难掌握,但只要不断地运用和遵守其准则,它将成为你的一部分,使你的代码易被扩展、修正,测试,即使重构也不简略出现问题。