使用这段真实的代码了解单一责任原则

时间:2017-06-08 10:31:18

标签: design-patterns language-agnostic single-responsibility-principle

因此,当您尝试自我教育单一责任原则时,您很可能会遇到诸如“一种方法应该只做一件事”这样的定义。和“一个班级应该只是一个改变的理由”。然后总是有很多玩具例子,比如

class Dog {
    String bark() {
         return "Woof";
    }
}

但我发现很难在企业应用程序开发中应用这个原则。

我们有一个应用程序,我们有“项目元素”,“活动”和“员工”。员工将拥有许多活动,而活动可以拥有一个项目元素。

某些员工被分配到所有项目元素,有些仅分配给一组。可以从Employee中删除项目元素,并可以分配新的元素。员工只能添加分配给她的项目元素的新活动。如果没有分配项目元素,这意味着所有项目都可用。

我们的客户有一个要求,他们希望在添加新活动时看到最喜欢的项目元素列表。员工如何收藏项目元素并不重要,它是一个不同的视图..

收藏项目元素必须按一些有趣的规则排序。 它取决于存在收藏项目元素的活动的总计算工作,仅考虑过去60天。至少这是具体要求..

所以我有责任实施这个,这是我的实施:

public List<ProjectElement> getFavoriteProjectElementsSortedDescendingByTotalWorkHoursInLast60Days(Employee owner) {
    // All Favorite Project Elements of Owner
    final List<ProjectElement> favoriteProjectElementsOfOwner
            = (List<ProjectElement>) (List<?>) favoriteBusinessObjectHelper.getAllFavoriteObjectsForBusinessObjectType(BusinessObjectType.PROJECT_ELEMENT.toInt());

    // Activities by Favorite Project Elements
    final List<Activity> favoriteProjectElementActivitiesForLast60Days
            = activityFinder.findByInProjectElementsForLastGivenDates(owner, favoriteProjectElementsOfOwner, 60);

    // Create a Map with Favorite Project Element - Calculated Work
    final Map<ProjectElement, Long> projectElementTotalWorkMap = new HashMap<ProjectElement, Long>();
    for (Activity activity : favoriteProjectElementActivitiesForLast60Days) {
        final ProjectElement activityProjectElement = activity.getProjectElement();
        if (!projectElementTotalWorkMap.containsKey(activityProjectElement)) {
            projectElementTotalWorkMap.put(activityProjectElement, 0L);
        }
        final long calculatedWork = activity.getCalculatedWork();
        final long totalCalculatedWork = projectElementTotalWorkMap.get(activityProjectElement) + calculatedWork;
        projectElementTotalWorkMap.put(activityProjectElement, totalCalculatedWork);
    }

    // Sort the Map by value descending
    final Map<ProjectElement, Long> sortedProjectElementTotalWorkMap
            = InnboundSortTool.sortByValueDescending(projectElementTotalWorkMap);

    // We do not want to show owners Favorite Project Element in the sidebar, if the Project Element is not available
    // for Employee anymore.. See the comments at the end of the file.
    final Set<ProjectElement> allowedProjectElementsForOwner = owner.getExplicitlyAssignedOnlyActiveProjectElements();

    final ArrayList<ProjectElement> projectElementsSorted = new ArrayList<ProjectElement>();
    for (ProjectElement projectElement : sortedProjectElementTotalWorkMap.keySet()) {
        if (allowedProjectElementsForOwner.size() == 0) { // This means Employee does not have any restrictions, all Project Elements are available to him.
            projectElementsSorted.add(projectElement);
        } else { // If Employee has assigned Project Elements, we must check if the Favorite Project Element is assigned to him..
            if (allowedProjectElementsForOwner.contains(projectElement)) {
                projectElementsSorted.add(projectElement); // If yes, add it to list, if no simply continue the loop without adding.
            }
        }
        if (projectElementsSorted.size() == 20) {
            break; // 20 is an arbitrary value, we do not want to show too many Favorite Project Elements in the UI.. Limit by 20.
        }
    }

    return projectElementsSorted;
}

哇,这是一个很大的方法,但它完成了工作。但它没有做一件事,是吗?但是,如果每种方法只做一件事,谁会做整件事?

我是否介绍了一个Helper类并将所有内容委托给该类并开始调用:

final Map<ProjectElement, Long> projectElementTotalWorkMap = helper.CreateprojectElementTotalWorkMap(); 

helper.removeUnassignedFavoriteProjectElementsFromEmployee();

等?但是,我会帮助Helper本身吗?它甚至在哪里结束?当我开始像这样重构时,我最终得到了非常无用的方法,它们只调用其他方法,这个类看起来像:

List<ProjectElement> favoritesList;
favoritesList = helper.doThis();
favoritesList = helper.doThat();
favoritesList = helper.sort();
return favoritesList;

我根本不理解这个原则吗?我想我没有,所以这就是问题,我应该如何解决这个方法,使其坚持“SRP”?

1 个答案:

答案 0 :(得分:1)

我认为要记住的重要一点是将你的责任放在同一抽象层面上。这意味着如果你把这个功能分成几个部分,是的,它会执行许多事情,但是,它将有一个责任 - 在较低的抽象层次上编排工作。

作为一个例子,这是你的函数的第一部分在一些重构之后:

public List<ProjectElement> getFavoriteProjectElementsSortedDescendingByTotalWorkHoursInLast60Days(Employee owner) {
    // All Favorite Project Elements of Owner
    final List<ProjectElement> favoriteProjectElementsOfOwner
            = (List<ProjectElement>) (List<?>) favoriteBusinessObjectHelper.getAllFavoriteObjectsForBusinessObjectType(BusinessObjectType.PROJECT_ELEMENT.toInt());

    // Activities by Favorite Project Elements
    final List<Activity> favoriteProjectElementActivitiesForLast60Days
            = activityFinder.findByInProjectElementsForLastGivenDates(owner, favoriteProjectElementsOfOwner, 60);

    // Create a Map with Favorite Project Element - Calculated Work
    final Map<ProjectElement, Long> projectElementTotalWorkMap = this.makeFacoriteProjectMap(favoriteProjectElementActivitiesForLast60Days)

生成projectElementTotalWorkMap的功能已移至私有函数。所以现在不再是你的主要职责,弄清楚如何构建地图。如果生成地图的方式有错误,您只需要查看将其移动到的功能:

private Map<ProjectElement, Long> projectElementTotalWorkMap makeFacoriteProjectMap(List<Activity> favoriteProjectElementActivitiesForLast60Days)
{
    Map<ProjectElement, Long> projectElementTotalWorkMap= new HashMap<ProjectElement, Long>();
    for (Activity activity : favoriteProjectElementActivitiesForLast60Days) {
        final ProjectElement activityProjectElement = activity.getProjectElement();
        if (!projectElementTotalWorkMap.containsKey(activityProjectElement)) {
            projectElementTotalWorkMap.put(activityProjectElement, 0L);
        }
        final long calculatedWork = activity.getCalculatedWork();
        final long totalCalculatedWork = projectElementTotalWorkMap.get(activityProjectElement) + calculatedWork;
        projectElementTotalWorkMap.put(activityProjectElement, totalCalculatedWork);
    }

    return projectElementTotalWorkMap;
}

以前,如果您在生成此地图的方式中发现错误,则必须更改主要功能。例如,如果事情以错误的顺序发生,那么主要功能也必须改变。

Orchestration现在只是主要职能的责任。

可以应用同样的重构来生成projectElementsSorted ArrayList,之后函数getFavoriteProjectElementsSortedDescendingByTotalWorkHoursInLast60Days将有1个职责 - 编排步骤,为您提供最近60个工作小时的排序列表天。

将有其他小型私人职能各自负责。即使有许多私人函数都在做自己的事情,这个代码所依赖的主要类仍然对外界负有责任。

为了配合你的狗吠例子,我将再做一个类比。让我们说你有一个培训师训练你的狗在马戏团里做一个例行公事。在一天结束时,狗必须能够做到这一点:

class Dog
{
    public void PerformRoutine()
    {
        this.bark();
        this.sit();
        this.walkInCircle();
        this.rollOver();
    }
    //...
}

即使这个函数做了很多事情,这个函数永远改变的唯一原因是如果例程改变了。但是,如果狗在吠叫时发出的声音需要改变,你就不会看到这里了。 PerformRoutine函数中的所有内容都处于相同的抽象层次。

在企业应用程序的上下文中,您经常有一个service / api层,用于编排域对象的保存,加载和调用。这些功能似乎违反了SR,但这些功能仍然是同一责任的一部分。保存和加载是您的存储库的责任。实际逻辑是域对象的责任。这些API /服务实际上只做了一件事 - 编排和委托给其他服务。

我希望这可以帮到你。

编辑:要回答您关于所有这些内容的价值的问题:

考虑没有SR的前一个狗示例:

class Dog
{
    public IEnumerator PerformRoutine()
    {
        this.audioController.PlaySound("c:/bar.mp3");
        this.animationControl.Play("Sit.anim");

        yield return new WaitForSeconds(2);

        this.animationController.Play("stand.anim");         

        yield return new WaitForSeconds(1);

        foreach(Vector3 pos in this.WalkPositions)
        {
            this.animationController.Play("walk.anim");
            this.position.lerpTo(pos, 5.0f); // move to position over 5 seconds.

        }

        this.animationController.Play("roll.anim");
    }
    //...
}

任何新的团队成员现在必须完全了解此功能的实现细节,以便了解它的真正功能。这已经大大简化了。

如果您询问新的团队成员(或者即使您重新访问旧代码),您最好还是要浏览一下并理解它的作用。功能名称&#34; PerformRoutine&#34;没有为读者准备动画或动作逻辑。

如果您被送回此代码,因为狗走的是方形而不是圆形,那么调整此代码就更难了。

现在,在具有数十万行代码和数千个函数的企业解决方案中应用相同的推理。它变得如此难以管理,最终你想要退出。

这是我为投资公司所做的真实生活整合的另一个例子。这里大概是第三方代码,它花了我几天的开发时间表:

public List<Event> getEventData()
{
    var toReturn = new List<Event>(this.eventData);
    this.eventData.Clear();
    return toReturn;
}

看哪。连续两次调用此函数会产生截然不同的输出,因为编写器不会打扰SR或正确命名。

只需付出最少的努力,就可以实现2个功能 - getEventDate()clearEventData()。我甚至可以使用不同的名称来解决1个函数 - getAndClearEventData(),尽管后者违反了SR。