文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

代码坏味道这个词与软件开发中的“反模式”概念比较类似. 有时在我们的代码中,我们不经意间就写入了坏味道的代码,然后这些坏代码会破坏我们的代码结构.

什么是代码坏味道

代码坏味道,反正就是一个不好的味道。在计算机编程领域中,代码坏味道就是指在源代码中表现出的任何症状,并且有可能引发深入问题的现象.

或者, 参照 Martin Fowler 说的那样, "坏代码味道是一个表面现象,但通常情况下它对应着系统中一个深入的问题."

代码坏味道会给我们在维护代码库或引入新功能时造成很多问题. 为此,开发者不得不编写一些重复的代码, 并且代码坏味道也会破坏代码的封装结构,破坏抽象关系等.

第 1 段(可获 1.33 积分)

因此, 开发过程中我们需要经常重构代码(用于剔除坏味道的代码).

本文中, 我们讨论下 “并行继承体系” 这种坏味道的代码.

当一棵继承树的组成依赖于另一颗继承树时,并行继承便发生了, 并且他们之间维持着一种特殊的关系,其中一个依赖继承的子类必须依赖另一个继承关系中特定的子类.

考虑一下工程师这个角色— 一般意义上的工程师. 计算机工程师从事计算机工作并且交付软件项目, 而土木工程师从事土木工程项目. 从设计的角度看,存在两种并行继承关系:

  • Engineers(工程师)

  • Milestones(里程碑)

第 2 段(可获 1.1 积分)

不同的工程师拥有不同的项目, 并且每一个工程师拥有一个明确的里程碑(明确的关系).

问题是每当你往工程师继承结构中加入一名工程师时,你都要相应的在里程碑继承体系中加入一个对应的里程碑对象.

导致“并行继承体系”的原因

  • 不理解对象职责,往往是由于误解 (破坏单一职责原则)
  • 对功能对象过度开发(指分离出很多不必要的接口?)
  • 没有使用适当的设计模式.
  • 太多重复的代码.
  • 错误的关系集 (客户端).
  • 代码库可维护性差.
第 3 段(可获 1.11 积分)

重构策略

我们可以通过使用 “移动方法” 和 “移动属性” 这种技巧完成重构.

现在,通过一个示例来展示一下并行继承体系. 我们将实现 Engineer 接口,并且给它添加一个 Milestone 对象.

package com.example.codesmell.parallelinheritence;

public interface Engineer {

    String getType();
    void setType(String type);
    int getSalary();
    void setSalary(int salary);
    MileStone getMileStone();
    void setMileStone(MileStone mileStone);
}

package com.example.codesmell.parallelinheritence;

public interface MileStone {
    public String work();
    public String target();
}

package com.example.codesmell.parallelinheritence;

public class ComputerEngineer implements Engineer {
    private String type;
    private int salary;
    private MileStone mileStone;
    public void setType(String type) {
        this.type = type;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    public void setMileStone(MileStone mileStone) {
        this.mileStone = mileStone;
    }

    @Override
    public String getType() {
        // TODO Auto-generated method stub
        return type;
    }

    @Override
    public int getSalary() {
        // TODO Auto-generated method stub
        return salary;
    }

    @Override
    public MileStone getMileStone() {
        // TODO Auto-generated method stub
        return mileStone;
    }

    @Override
    public String toString() {
        return "ComputerEngineer [type=" + type + ", salary=" + salary
        + ", mileStone=" + mileStone + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class ComputerMileStone implements MileStone {

    @Override
    public String work() {
        return"Build a Billing MicroService";
    }

    @Override
    public String target() {
        return"Has to be finshed in 14 PD";
    }

    @Override
    public String toString() {
        return "ComputerMileStone [work()=" + work() + ", target()=" + target()
        + "]";
    }
}


package com.example.codesmell.parallelinheritence;

public class CivilEngineer implements Engineer {

    private String type;
    private int salary;
    private MileStone mileStone;

    public void setType(String type) {
        this.type = type;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

    public void setMileStone(MileStone mileStone) {
        this.mileStone = mileStone;
    }

    @Override
    public String getType() {
        // TODO Auto-generated method stub
        return type;
    }

    @Override
    public int getSalary() {
        // TODO Auto-generated method stub
        return salary;
    }

    @Override
    public MileStone getMileStone() {
        // TODO Auto-generated method stub
        return mileStone;
    }

    @Override
    public String toString() {
        return "CivilEngineer [type=" + type + ", salary=" + salary
        + ", mileStone=" + mileStone + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class CivilMileStone implements MileStone {

    @Override
    public String work() {
        // TODO Auto-generated method stub
        return "Create  Twin Towers";
    }

    @Override
    public String target() {
        // TODO Auto-generated method stub
        return "Has to be completed in 2 years";
    }

    @Override
    public String toString() {
        return "CivilMileStone [work()=" + work() + ", target()=" + target()
        + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class Manager {
    public static void main(String[] args) {

        Engineer comp = new ComputerEngineer();
        comp.setType("Computer Engineer");
        comp.setSalary(50000);
        comp.setMileStone(new ComputerMileStone());

        Engineer civil = new CivilEngineer();
        civil.setType("Civil Engineer");
        civil.setSalary(60000);

        civil.setMileStone(new CivilMileStone());

        System.out.println(comp);
        System.out.println("********************");
        System.out.println(civil);
    }
}


Output : 

ComputerEngineer [type=Computer Engineer, salary=50000, mileStone=ComputerMileStone [work()=Build a Billing MicroService, target()=Has to be finshed in 14 PD]]
********************
CivilEngineer [type=Civil Engineer, salary=60000, mileStone=CivilMileStone [work()=Create  Twin Towers, target()=Has to be completed in 2 years]]
第 4 段(可获 0.44 积分)

我们建立了两个接口 — 工程师接口和里程碑接口 — 并且给他们添加了接口方法, 但我们需要注意的是每一个工程师都有他自己特定的里程碑对象, 因此我们需要把setMileStone方法暴露给客户端, 不过这有可能导致客户端程序猿为工程师对象设置错误的MileStone.

另一个需要注意的是党我们创建一个新的工程师对象时,我们也需要为他/她增加一个里程碑对象. 这个问题比较难办. 实际上如果我们尝试修改的话,可能破坏 SRP (单一职责原则)原则.

不过我们可以使用下面的三种方法.

第 5 段(可获 1.34 积分)

方法 1

继续保持并行继承关系并适应它.

优点

  • 更好的遵循 SRP 原则.

  • 代码灵活.

缺点

  • 要增加一个新功能,不得不每次都创建两个类.

  • 继承层次存在耦合关系. 改变其中一个需要对应改变另外的类或方法.

  • 难以维护.

方法 2

将他们分开为局部继承结构,这样就可以提供开放的并行层次结构.

有点

  • 只需要维护一个继承关系.

  • 当不确定应该是谁的职责时,可以尽管使用这个职责.

  • 代码灵活性.

Cons

  • 可能打破 SRP 原则

使用技巧

创建一个实现两个接口的具体的类. 让客户端通过静态工厂方法获取实例对象.

第 6 段(可获 1.4 积分)

来看一下解决办法:

package com.example.codesmell.parallelinheritence;

public class PartialComputerEngineer implements Engineer,MileStone {

    private String type;
    private int salary; 

    @Override
    public String work() {
        return"Build a Billing MicroService";
    }

    @Override
    public String target() {
        return"Has to be finshed in 14 PD";
    }

    @Override
    public String getType() {
        // TODO Auto-generated method stub
        return type;
    }

    @Override
    public void setType(String type) {
        this.type=type;
    }

    @Override
    public int getSalary() {
        // TODO Auto-generated method stub
        return salary;
    }

    @Override
    public void setSalary(int salary) {
        this.salary=salary;
}

    @Override
    public MileStone getMileStone() {
        // TODO Auto-generated method stub
        return this;
    }

    @Override
    public void setMileStone(MileStone mileStone) {
        throw new UnsupportedOperationException("Not Supported");
}

    @Override
    public String toString() {
        return "PartialComputerEngineer [type=" + type + ", salary=" + salary
        + ", work()=" + work() + ", target()=" + target()
        + ", getType()=" + getType() + ", getSalary()=" + getSalary()
         + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class PartialCivilEngineer  implements Engineer,MileStone {
    private String type;
    private int salary; 

    @Override
    public String work() {
        return "Create  Twin Towers";
    }

    @Override
    public String target() {
        return "Has to be completed in 2 years";
    }

    @Override
    public String getType() {
        return type;
    }

    @Override
    public void setType(String type) {
        this.type=type;
}

    @Override
    public int getSalary() {
        // TODO Auto-generated method stub
        return salary;
    }

    @Override
    public void setSalary(int salary) {
        this.salary=salary;
    }

    @Override
    public MileStone getMileStone() {
        // TODO Auto-generated method stub
        return this;
    }

    @Override
    public void setMileStone(MileStone mileStone) {
        throw new UnsupportedOperationException("Not Supported");
    }

    @Override
    public String toString() {
        return "PartialCivilEngineer [type=" + type + ", salary=" + salary
        + ", work()=" + work() + ", target()=" + target()
        + ", getType()=" + getType() + ", getSalary()=" + getSalary()
        + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class EngineerFactory {

    public static Engineer getEngineer(Class clazz) throws InstantiationException, IllegalAccessException
    {
        return (Engineer) clazz.newInstance();
    }
}

package com.example.codesmell.parallelinheritence;

public class Manager {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {

        Engineer comp = EngineerFactory.getEngineer(PartialComputerEngineer.class);
        comp.setType("Computer Engineer");
        comp.setSalary(50000);

        Engineer civil = EngineerFactory.getEngineer(PartialCivilEngineer.class);
        civil.setType("Computer Engineer");
        civil.setSalary(50000);

        System.out.println(comp);
        System.out.println("********************");
        System.out.println(civil);
    }
}


Output :
PartialComputerEngineer [type=Computer Engineer, salary=50000, work()=Build a Billing MicroService, target()=Has to be finshed in 14 PD, getType()=Computer Engineer, getSalary()=50000]
********************
PartialCivilEngineer [type=Computer Engineer, salary=50000, work()=Create  Twin Towers, target()=Has to be completed in 2 years, getType()=Computer Engineer, getSalary()=50000]

第 7 段(可获 0.06 积分)

方法 3(我觉得这种方法不爽)

折叠继承关系.

优点

  • 只需维护一棵继承树

  • 便于维护

缺点

  • 彻底打破 SRP 原则.

使用技巧

建立一个通用的接口,把其他接口的方法拿过来.

看一下解决方法:

package com.example.codesmell.parallelinheritence;

public interface EngineerMileStone {

    String getType();
    void setType(String type);
    int getSalary();
    void setSalary(int salary);
    public String work();
    public String target();
}

package com.example.codesmell.parallelinheritence;

public class RefactorComputerEngineer implements EngineerMileStone {

    private String type;
    private int salary; 

    @Override
    public String getType() {
        return type;
    }

    @Override
    public void setType(String type) {
        this.type=type;
    }

    @Override
    public int getSalary() {
        return salary;
    }

    @Override
    public void setSalary(int salary) {
        this.salary=salary;
    }

    @Override
    public String work() {
        return"Build a Billing MicroService";
    }

    @Override
    public String target() {
        return"Has to be finshed in 14 PD";
    }

    @Override
    public String toString() {
        return "RefactorComputerEngineer [type=" + type + ", salary=" + salary
        + ", getType()=" + getType() + ", getSalary()=" + getSalary()
        + ", work()=" + work() + ", target()=" + target() + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class ReFactorCivilEngineer implements EngineerMileStone {

    private String type;
    private int salary; 

    @Override
    public String getType() {
        return type;
    }

    @Override
    public void setType(String type) {
        this.type=type;
    }

    @Override
    public int getSalary() {
        return salary;
    }

    @Override
    public void setSalary(int salary) {
        this.salary=salary;
    }

    @Override
    public String work() {
        return "Create  Twin Towers";
    }

    @Override
    public String target() {
        return "Has to be completed in 2 years";
    }

    @Override
    public String toString() {
        return "ReFactorCivilEngineer [type=" + type + ", salary=" + salary
        + ", getType()=" + getType() + ", getSalary()=" + getSalary()
        + ", work()=" + work() + ", target()=" + target() + "]";
    }
}

package com.example.codesmell.parallelinheritence;

public class Manager {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {

        EngineerMileStone comp = new RefactorComputerEngineer();
        comp.setType("Computer Engineer");
        comp.setSalary(50000);

        EngineerMileStone civil = new ReFactorCivilEngineer();
        civil.setType("Civil Engineer");
        civil.setSalary(60000);

        System.out.println(comp);
        System.out.println("********************");
        System.out.println(civil);
    }
}

Output

RefactorComputerEngineer [type=Computer Engineer, salary=50000, getType()=Computer Engineer, getSalary()=50000, work()=Build a Billing MicroService, target()=Has to be finshed in 14 PD]
********************
ReFactorCivilEngineer [type=Civil Engineer, salary=60000, getType()=Civil Engineer, getSalary()=60000, work()=Create  Twin Towers, target()=Has to be completed in 2 years]

 

第 8 段(可获 0.43 积分)

文章评论