继承和组合混淆?

inheritance and composition confusion?

我有一个摘要 class Area,它被子class编辑为 ProvinceDivisionDistrictCity.

现在,我需要在 City class 中指定此 city 存在于哪个 district 中。所以,我将在 City class(组合)中有一个 District class 的实例,这样我就可以传递特定 [=16= 的 id ] 到 city 并将存储在数据库城市表中。但是,它不遵循组合规则。作为地区有城市而不是其他方式。

还有一个问题是class都是用继承和组合,我觉得不对。

一周以来,我一直在尝试通过谷歌搜索和其他方法自行解决这个问题。但是,我无法解决这个问题。我想这是我最后的希望了。 我将如何解决这个问题?有什么例子吗?

非常有趣的问题,但缺少一个重要细节 - 上下文。什么会创造城市,什么会进入城市?负责哪些市、区等?它们只是数据实体吗?在我可以帮助你之前,我必须回答这些问题。那么让我们开始设计我们的领域模型吧:

客户端(假设为main方法)将通过CountryBuilder接口创建场所。客户端将通过 Registry 接口访问它们。地点将是不可变的(不允许客户端直接修改地点数据)。客户端允许的现有位置的唯一变化是通过 CountryBuilder 添加一个新位置。所有地方 都有 并且(根据您的要求)知道(有名称)它是封闭的地方。 State没有围场,但可以拥有DistrictsDistrict 的名称为 State 并包含 Cities,城市不能包含地点,但具有其所有者的名称 (ZipAddress)。当然你可以只使用一个抽象Place来达到同样的效果,但是你需要使用一些检查来确定这个地方是什么,因为不是所有的地方都可以包含其他地方(例如城市),不是所有的地方都是被其他地方(例如州)包含,有些地方可以包含其他地方,也可以被某个地方(地区)包含。为了避免检查,需要这样做才能知道那个地方是城市、地区还是州我使用了三种不同的抽象。您可以在不创建 CityDistrict 的情况下创建 State,但在不指定 StateDistrict 的情况下无法创建 City。请仔细阅读代码并阅读我下面的评论:

CountryClient.java 这是一个客户class。它只能访问国家 class 的两个工厂方法。

package com.ooDesign;

import com.ooDesign.Country.Country;
import com.ooDesign.Country.Country.City;
import com.ooDesign.Country.Country.District;
import com.ooDesign.Country.Country.State;
import com.ooDesign.Country.Registry.NoSuchPlaceException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class CountryClient 
{
    public static void main(String[] args)
    {
        /*Creating various combinations of places.*/      
        build("ImaginaryState" , "ImaginaryDistrict", "MadCity");
        build("ImaginaryState" , "ImaginaryDistrict", "EastCity");
        build("ImaginaryState" , "ImaginaryDistrict", "WestCity");
        build("DamnGoodState" , "DamnGoodDistrict", "DamnGoodCity");
        build("ImaginaryState" , "ProgrammersDistrict", "NoLifersCity");
        build("DamnGoodState" , "DamnGoodDistrict", "DamnBadCity");
        /*"DamnGoodCity" in "DamnBadDistrict" is not the same as "DamnGoodCity" in "DamnGoodDistrict"
           since they are located in different districts. You can easily find out how to change implementation
           to not allow to build multiple cities with same name even if they are in different districts.*/
        build("DamnGoodState" , "DamnBadDistrict", "DamnGoodCity");

        /*Printing what we just created*/
        try
        {
            traverseWorld();
        } catch (NoSuchPlaceException ex)
        {
            Logger.getLogger(CountryClient.class.getName()).log(Level.SEVERE, null, ex);
        }

        /*Getting state of speciffic city (thanks to ZipCode interface)*/
        try
        {
            print(Country.registry().state("DamnGoodState").district("DamnBadDistrict").city("DamnGoodCity").zipCode().state());
        } catch (NoSuchPlaceException ex)
        {
            Logger.getLogger(CountryClient.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    static void print(String string)
    {
        System.out.println(string);
    }

    static void traverseWorld() throws NoSuchPlaceException
    {
        for(State s : Country.registry())
        {
            print("Districts in state \"" + s.name() + "\" :");
            for(District d : s)
            {
                print("   Cities in district \"" + d.name() + "\" :");
                for(City c : d)
                {
                    print("      " + c.name());
                }
            }
            print("---------------------------------------------");
        }
    }

    static void build(String state, String district, String city)
    {
        Country.builder().build().state(state).district(district).city(city);
    }

    static void build(String state, String district)
    {
        Country.builder().build().state(state).district(district);
    }

    static void build(String state)
    {
        Country.builder().build().state(state);
    }
}

Country.java 数据实体接口(城市、地区、州)的持有者以及访问器(注册表)和 Muttator(国家建设者)抽象的静态工厂。

package com.ooDesign.Country;

import java.util.HashMap;
import com.ooDesign.Country.Registry.NoSuchPlaceException;

public final class Country
{
    private static HashMap<String, State> states = new HashMap<>();

    public static CountryBuilder builder()
    {
        return new CountryBuilderImpl(states);
    }

    public static Registry registry()
    {
        return new RegistryImpl(states);
    }

    public interface Place
    {
        String name();
    }

    public interface State extends Place, Iterable<District>
    {
        public District district(String districtName) throws NoSuchPlaceException;
    }

    public interface District extends Place, Iterable<City>
    {
        public City city(String cityName) throws NoSuchPlaceException;
        public String inState();
    }

    public interface City extends Place
    {
        public ZipCode zipCode();
    }

    public interface ZipCode
    {
        String state();
        String district();
        String city();
    }
}

CountryBuilder.java 我喜欢这种构建组合对象的方式,因为它的可读性。然后你可以像这样实例化对象 Builder.build().firstObject(irstparams).secondObject(secondParams)...etc

package com.ooDesign.Country;

public interface CountryBuilder
{
    public StateBuilder build();

    public interface StateBuilder
    {
       public DistrictBuilder state(String stateName);
    }

    public interface DistrictBuilder
    {
        public CityBuilder district(String districtName);
    }

    public interface CityBuilder
    {
        public void city(String cityName);
    }

}

CountryBuilderImpl.java CountryBuilder 抽象的实现。

package com.ooDesign.Country;

import com.ooDesign.Country.Country.State;
import static com.ooDesign.Country.Country.*;
import com.ooDesign.Country.Registry.NoSuchPlaceException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

class CountryBuilderImpl implements CountryBuilder
{
    private Map<String, State> states;

    public CountryBuilderImpl(Map<String, State> states)
    {
        this.states = states;
    }

    @Override
    public StateBuilder build()
    {
        return new StateBuilder()
        {
            @Override
            public DistrictBuilder state(String stateName)
            {

                StateImpl currentState;
                if (states.containsKey(stateName))
                {
                    currentState = (StateImpl)states.get(stateName);
                } else
                {
                    currentState = new StateImpl(stateName);
                    states.put(stateName, currentState);
                }

                return new DistrictBuilder()
                {
                    @Override
                    public CityBuilder district(String districtName)
                    {
                        DistrictImpl currentDistrict = currentState.addDistrict(districtName);

                        return new CityBuilder()
                        {
                            @Override
                            public void city(String cityName)
                            {
                                currentDistrict.addCity(cityName);
                            }
                        };
                    }
                };
            }
        };
    }

    private static class StateImpl implements State
    {

        private final Map<String, District> districts;
        private final String stateName;

        StateImpl(String stateName)
        {
            this.districts = new HashMap<>();
            this.stateName = stateName;
        }

        DistrictImpl addDistrict(String districtName)
        {
            if (!districts.containsKey(districtName))
            {
                districts.put(districtName, new DistrictImpl(stateName, districtName));
            }
            return (DistrictImpl)districts.get(districtName);
        }

        @Override
        public District district(String districtName) throws Registry.NoSuchPlaceException
        {
            if (!districts.containsKey(districtName))
            {
                throw new Registry.NoSuchPlaceException("District \"" + districtName + "\" in state of " + stateName + " does not exists");
            } else
            {
                return districts.get(districtName);
            }
        }

        @Override
        public String name()
        {
            return stateName;
        }

        @Override
        public Iterator<Country.District> iterator()
        {
            return districts.values().iterator();
        }

    }

    private static class DistrictImpl implements District
    {

        private final Map<String, Country.City> cities;
        private final String stateName, districtName;

        DistrictImpl(String stateName, String districtName)
        {
            this.cities = new HashMap<>();
            this.stateName = stateName;
            this.districtName = districtName;
        }

        void addCity(String cityName)
        {
            if (!cities.containsKey(cityName))
            {
                cities.put(cityName, new CityImpl(new ZipImpl(stateName, districtName, cityName)));
            }
        }

        @Override
        public City city(String cityName) throws NoSuchPlaceException
        {
            if (!cities.containsKey(cityName))
            {
                throw new Registry.NoSuchPlaceException("City \"" + cityName + "\" in state of " + stateName + " in district of " + districtName + " does not exists");
            } else
            {
                return cities.get(cityName);
            }
        }

        CityImpl getCity(String cityName)
        {
            return (CityImpl)cities.get(cityName);
        }

        @Override
        public String inState()
        {
            return stateName;
        }

        @Override
        public String name()
        {
            return districtName;
        }


        @Override
        public Iterator<Country.City> iterator()
        {
            return cities.values().iterator();
        }

    }

    private static class CityImpl implements City
    {

        private final Country.ZipCode zipCode;

        public CityImpl(Country.ZipCode zipCode)
        {
            this.zipCode = zipCode;
        }

        @Override
        public Country.ZipCode zipCode()
        {
            return zipCode;
        }

        @Override
        public String name()
        {
            return zipCode.city();
        }

    }

    private static class ZipImpl implements ZipCode
    {

        private final String state, district, city;

        public ZipImpl(String state, String district, String city)
        {
            this.state = state;
            this.district = district;
            this.city = city;
        }

        @Override
        public String state()
        {
            return state;
        }

        @Override
        public String district()
        {
            return district;
        }

        @Override
        public String city()
        {
            return city;
        }

        public String toString()
        {
            return "ZIP_CODE: STATE - " + state + "; DISTRICT - " + district + "; CITY - " + city;
        }
    }
}

Registry.java 用于访问地点。

package com.ooDesign.Country;

import com.ooDesign.Country.Country.State;
import java.util.Set;

public interface Registry extends Iterable<State>
{
    Set<String> listStates();
    State state(String stateName) throws NoSuchPlaceException;

    public static class NoSuchPlaceException extends Exception
    {
        public NoSuchPlaceException(String message)
        {
            super(message);
        }  
    }
}

RegistryImpl.java 名字说明了它的用途。

package com.ooDesign.Country;

import com.ooDesign.Country.Country.State;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

class RegistryImpl implements Registry
{
    private final Map<String, State> states;

    public RegistryImpl(Map<String, State> states)
    {
        this.states = states;
    }

    @Override
    public Set<String> listStates()
    {
        return states.keySet();
    }

    @Override
    public State state(String stateName) throws NoSuchPlaceException
    {
        if(!states.containsKey(stateName)) 
            throw new NoSuchPlaceException("State \"" + stateName + "does not exists");
        return states.get(stateName);
    }

    @Override
    public Iterator<State> iterator()
    {
        return states.values().iterator();
    }

}

如您所见,实现与客户端是隔离的,因为它们位于不同的包中,并且实现 class 不是 public。客户端只能通过接口与它们交互。接口有很多用途和优点。它们是面向对象设计的核心。我会留给你找出如何获得特定州的所有城市,特定州的所有地区,特定地区的所有城市等。这很容易做到。如果您愿意,您可以实施许多方便的方法、各种管理 classes(如果您正在编写高质量、可维护的软件,您 必须 )。所有这些代码只是为了向您展示 OO 设计的全景图。这真是太棒了,你整个星期都充满热情地寻求答案。如果您开始学习新概念,我的建议是找一本好书并阅读它。 OO 设计和软件架构非常广泛。而且很漂亮。但是如果你想看到它的所有荣耀,你需要掌握它。阅读 Holub on patterns 一书,它一定会对您有所帮助。 P.S 欢迎提出有关代码的问题,如果发现错误请通知我。祝你好运!

我只是在这里看到抽象。您有一个 Area,它可以是 ProvinceDivisionDistrictCity,但就继承而言,这就是关系的发展范围.当然,这确实提出了问题,"What would a province, division, district, or city have enough of in common with one another besides a name that would be enough to create an inheritance hierarchy for, instead of an interface?"

请记住,继承(在某种程度上,接口)总是定义为 is-a 关系。也就是说,就你现在的系统而言,District 是一个 Area,而City 是-一个Area。这两者在纸面上听起来都不错,而且这种表示形式并没有什么错。事实上,您可以将其移动到一个界面,这可能会很好。

你想要与那个想法分开的是 has-a 关系,这就是组合所引入的。

关系可能是双向的,但这里的主要前提是:

  • 一个City 有一个 District.
  • 一个City 有一个 Division.
  • 一个City 有一个 Province.

...而以上三个也有相同对应的City.

这两种方法都可以,但是你要清楚:

  • 为什么你在使用它们,并且
  • 什么时候用起来最合适

在这种情况下,确实感觉继承最没有意义,因为 getName() 方法可以 轻松地 由四、不需要带继承。