web-dev-qa-db-ja.com

継承を使用して3つの非常に類似したクラスをリファクタリングしますか?

現在、サービスのコードベースのリファクタリングに取り組んでいます。私はすべてを見直しているところですが、それは少しばらばらで、おそらくOOPの原則の方が優れていると思います。

私はすべて別のクラスCacheから派生した3つのクラスを持っています。これら3つのクラスはすべてまったく同じ操作を実行します。唯一の違いは、クエリ対象のオブジェクトのタイプと、それらのクエリに使用するメソッド呼び出しです。これらのクラスをさらにシンプルにする最良の方法は何でしょうか?

public static class ZenDeskCache
{
    public static ZendeskApi Api = new ZendeskApi(GlobalVariables.ZendeskUrl, GlobalVariables.ZendeskUser,
        GlobalVariables.ZendeskPass);

    public class Users : Cache<Users, List<User>>
    {
        protected override List<User> GetData()
        {
            var users = Api.Users.GetAllUsers();
            var allUsers = new List<User>(users.Users);

            while (users.NextPage != null)
            {
                users = Api.Users.GetByPageUrl<GroupUserResponse>(users.NextPage);
                allUsers.AddRange(new List<User>(users.Users));
            }

            allUsers = allUsers.OrderBy(n => n.Name).ToList();

            return allUsers;
        }

        protected override TimeSpan GetLifetime()
        {
            return TimeSpan.FromDays(1);
        }
    }

    public class Organizations : Cache<Organizations, List<Organization>>
    {
        protected override List<Organization> GetData()
        {
            var organizations = Api.Organizations.GetOrganizations();
            var allOrgs = new List<Organization>(organizations.Organizations);

            while (organizations.NextPage != null)
            {
                organizations = Api.Users.GetByPageUrl<GroupOrganizationResponse>(organizations.NextPage);
                allOrgs.AddRange(new List<Organization>(organizations.Organizations));
            }

            allOrgs = allOrgs.OrderBy(n => n.Name).ToList();

            return allOrgs;
        }

        protected override TimeSpan GetLifetime()
        {
            return TimeSpan.FromDays(1);
        }
    }

    public class Groups : Cache<Groups, List<Group>>
    {
        protected override List<Group> GetData()
        {
            var groups = Api.Groups.GetGroups();
            var allGroups = new List<Group>(groups.Groups);

            while (groups.NextPage != null)
            {
                groups = Api.Groups.GetByPageUrl<MultipleGroupResponse>(groups.NextPage);
                allGroups.AddRange(new List<Group>(groups.Groups));
            }

            allGroups = allGroups.OrderBy(n => n.Name).ToList();

            return allGroups;
        }

        protected override TimeSpan GetLifetime()
        {
            return TimeSpan.FromSeconds(600);
        }
    }
}
7
JD Davis

問題は、基になるAPIの設計lovesがすべてのプロパティ名で型情報を繰り返すことです。

_... Api.Groups.GetGroups();
... groups.Groups ...

    ... Api.Groups.GetByPageUrl<MultipleGroupResponse>(...);
    ... groups.Groups ...
_

この誤った設計により、その他すべての点で同一の_Api.X_オブジェクトを抽象化することが困難になります。特に、メソッドは次の点で異なります。

  • メソッドが呼び出される_Api.X_オブジェクト
  • 初期応答を取得するApi.X.GetX()メソッドの名前
  • 1つの応答でデータにアクセスするプロパティ_x.X_の名前
  • GetPageByUrlからの応答のタイプ

解決策は、これらの違いを平準化する適切なレイヤーを導入することです。例えば。次のように呼び出すことができる共通の関数CommonGetDataを定義できます。[1]

[1]:私はC#に堪能ではないので、構文の詳細ではなく概念的な設計のみに注意してください。

_protected override List<User> GetData()
{
    return CommonGetData(
        Api.Users,
        (api) => api.GetAllUsers(),
        (response) => response.Users,
    );
}
_

CommonGetDataは、おおよそ次のように定義されています。

_private static <T, ResponseT, ApiT> List<T> CommonGetData(
        ApiT api,
        Function<ResponseT, ApiT> getInitialResponse,
        Function<Collection<T>, ResponseT> itemsFromResponse,
    )
{
    ResponseT response = getInitialResponse(api);
    var allItems = new List<T>(itemsFromResponse(response);

    while (response.NextPage != null)
    {
        response = api.GetPageByUrl<ResponseT>(response.NextPage);
        allItems.AddRange(new List<T>(itemsFromResponse(response));
    }

    allItems = allItems.OrderBy(n => n.Name).ToList();

    return allItems;
}
_

ラムダを使用することが目的でない場合は、これが戦略パターン(CommonGetDataでは、コールバックパラメータは戦略を表す)またはテンプレートメソッドパターンの候補のように見えることにも注意してください。次に、次のように抽象APIを定義できます。

_abstract class CommonApi<T, ResponseT>
{
    protected abstract ResponseT GetInitialResponse();
    protected abstract Collection<T> ItemsFromResponse(ResponseT response);
    protected abstract GetPageByUrl(Url url);

    private List<T> GetData()
    {
        ResponseT response = GetInitialResponse();
        var allItems = new List<T>(ItemsFromResponse(response);

        while (response.NextPage != null)
        {
            response = GetPageByUrl(response.NextPage);
            allItems.AddRange(new List<T>(ItemsFromResponse(response));
        }

        allItems = allItems.OrderBy(n => n.Name).ToList();

        return allItems;
    }
}

class UsersApi : CommonApi<User, MultipleUserResponse>
{
    protected override MultipleUserResponse GetInitialResponse()
    {
        return Api.Users.GetAllUsers();
    }

    protected override Collection<User> ItemsFromResponse(MultipleUserResponse response)
    {
        return response.Users;
    }

    protected override GetPageByUrl(Url url)
    {
        Api.Users.GetPageByUrl<MultipleUserResponse>(url);
    }
}
_

のように使用されます

_private UsersApi usersApi = new UsersApi();

protected override List<User> GetData()
{
    return usersApi.GetData();
}
_

理想的には、既存のAPIをリファクタリングして共通インターフェースを実装し、互換性のないメソッドに委譲する共通インターフェースでそれらをラップする必要がないようにする必要があります。

また、静的クラスを非常に頻繁に使用すると、コードの再利用が妨げられる可能性があります。これは、これらを値として渡すことが困難になるためです。インスタンスが1つしか存在しない場合は、静的クラスと比較した場合にクラスのテストが容易になるシングルトンパターンを使用してください。

5
amon

really継承を使用してこれらのクラスをリファクタリングしたい場合は、結局のところ、amonの答えは正解です。

ただし、小さなコードの重複をなくすことは、継承を使用して設計を複雑にするもっとも正当な理由の1つであることを忘れないでください。結果のコードが元のコードよりも詳細である場合、可読性、理解可能性、および保守性の純損失があります。

これは実際には少しコードを複製するよりも悪いかもしれません。

1
Mike Nakis

@Amonによって投稿された情報のいくつかを確認した後、私は少しすっきりしたものを持っていると思います。より冗長ですが、よりクリーンなようです。

CommonApi

public abstract class CommonApi<T, TResponse>
    where TResponse : GroupResponseBase
{
    protected abstract TResponse GetInitialResponse();
    protected abstract List<T> ItemsFromResponse(TResponse response);
    protected abstract TResponse GetPageByUrl(string url);

    public ZendeskApi Api = new ZendeskApi(
        GlobalVariables.ZendeskUrl,
        GlobalVariables.ZendeskUser,
        GlobalVariables.ZendeskPass);

    public List<T> GetData()
    {
        var response = GetInitialResponse();
        var allItems = new List<T>(ItemsFromResponse(response));

        while (response.NextPage != null)
        {
            response = GetPageByUrl(response.NextPage);
            allItems.AddRange(ItemsFromResponse(response));
        }

        allItems = SortData(allItems);

        return allItems;
    }

    private List<T> SortData(List<T> list)
    {
        return list;
    }

    public List<User> SortData(List<User> list)
    {
        return list.OrderBy(n=>n.Name).ToList();
    }

    public List<Group> SortData(List<Group> list)
    {
        return list.OrderBy(n => n.Name).ToList();
    }

    public List<Organization> SortData(List<Organization> list)
    {
        return list.OrderBy(n => n.Name).ToList();
    }
}

UserListApi

public class UserListApi : CommonApi<User, GroupUserResponse>
{
    protected override GroupUserResponse GetInitialResponse()
    {
        return Api.Users.GetAllUsers();
    }

    protected override List<User> ItemsFromResponse(GroupUserResponse response)
    {
        return response.Users.ToList();
    }

    protected override GroupUserResponse GetPageByUrl(string url)
    {
        return Api.Users.GetByPageUrl<GroupUserResponse>(url);
    }
}

OrganizationListApi

public class OrganizationListApi : CommonApi<Organization, GroupOrganizationResponse>
{
    protected override GroupOrganizationResponse GetInitialResponse()
    {
        return Api.Organizations.GetOrganizations();
    }

    protected override List<Organization> ItemsFromResponse(GroupOrganizationResponse response)
    {
        return response.Organizations.ToList();
    }

    protected override GroupOrganizationResponse GetPageByUrl(string url)
    {
        return Api.Organizations.GetByPageUrl<GroupOrganizationResponse>(url);
    }
}

GroupListApi

public class GroupListApi : CommonApi<Group, MultipleGroupResponse>
{
    protected override MultipleGroupResponse GetInitialResponse()
    {
        return Api.Groups.GetGroups();
    }

    protected override List<Group> ItemsFromResponse(MultipleGroupResponse response)
    {
        return response.Groups.ToList();
    }

    protected override MultipleGroupResponse GetPageByUrl(string url)
    {
        return Api.Groups.GetByPageUrl<MultipleGroupResponse>(url);
    }
}

ZendeskCache

public class ZendeskCache
{
    public class Users : Cache<Users, List<User>>
    {
        protected override List<User> GetData()
        {
            var users = new UserListApi();

            return users.GetData();
        }

        protected override TimeSpan GetLifetime()
        {
            return TimeSpan.FromDays(1);
        }
    }

    public class Organizations : Cache<Organizations, List<Organization>>
    {
        protected override List<Organization> GetData()
        {
            var orgs = new OrganizationListApi();

            return orgs.GetData();
        }

        protected override TimeSpan GetLifetime()
        {
            return TimeSpan.FromDays(1);
        }
    }

    public class Groups : Cache<Groups, List<Group>>
    {
        protected override List<Group> GetData()
        {
            var groups = new GroupListApi();
            return groups.GetData();
        }

        protected override TimeSpan GetLifetime()
        {
            return TimeSpan.FromSeconds(1);
        }
    }
}

ZendeskCacheをさらに削減することは可能かもしれませんが、いじくり続けます。私は、将来のコーディングプラクティスをよりクリーンでメンテナンスしやすいものにする方法を見つけ出そうとしています。

0
JD Davis

さまざまなデータクラスの型変数を持つジェネリッククラスを作成できますか?

public class WhateverCache<T> : Cache<T, List<T>> {
  protected override List<T> GetData() {
    // whatever logic you have
  }

特定のクラスは、期間を定義するメソッドのみをオーバーライドします。

0
9000