将继承与 parent class 一起使用,其中包含仅适用于 child classes 的变量的空字符串

Using inheritance with a parent class which contains empty strings for variables that are only applicable in child classes

我正在尝试在 OOP 中对地理位置建模。位置类型为 follows:continent、国家、州、县或更具体的(例如城市、城镇、村庄,全部建模为单一类型)。大陆有大陆代码,县有大陆代码和国家代码,州有大陆代码,国家代码和adm1code,县和比县更具体的有大陆代码,国家代码,adm1code和adm2code。我需要所有位置都使用方法 "isContainedIn(Location loc)" 实现一个接口。这将根据管理代码检查当前位置是否包含在 loc 中。因此,对于国家/地区,此方法将首先检查 loc 是否为大陆 - 如果不是,则 return false。如果是大陆,则检查loc的大陆代码是否与国家的大陆代码相同。

我可以使用带有类型字段的单个 class 或使用继承来为位置建模。我宁愿使用继承来建模,基数 class 称为 location,表示比 adm2 更具体的位置,所有行政位置类型(国家、代码、州和县)都将扩展(这在概念上是不正确的——我是说town 是 parent class 大陆)。这样我也可以简单地覆盖其他方法,例如等于运算符(例如,如果两个国家具有相同的国家代码,则两个国家是平等的)。然而,基础 class 将需要包括大陆代码、国家代码、adm1code 和 adm2code 以允许我在每种情况下实现 isContainedIn(Location loc) 方法。国家代码对大洲没有意义,adm1code 对国家没有意义等等。当它们没有意义时,我可以使用空白字符串,但这会违反 Liskov 替换原则或任何其他标准设计原则吗?如果是这样,您能否针对此问题提出其他设计方案?

编辑:我希望客户端 class 获得两个位置实例,比如 l1 和 l2,并且能够在不知道位置的具体类型的情况下调用 l1.isContainedIn(l2)。如果我按如下方式对 classes 进行建模,让我详细说明它是如何成为一个问题的: 1. parent class 仅包含对所有位置有意义的变量,即名称、纬度和经度 2. Children classes(大陆、国家、州、县等),全部实现 isContainedIn(Location loc) 接口。大陆会有大陆代码,国家会有大陆代码和国家代码等等。

使用这种结构,我根本无法在任何 child class 中写出 isContainedIn(Location loc) 的逻辑。例如,如果 1) 它不是大陆并且 2) 它具有与大陆相同的大陆代码,则该位置包含在大陆中。但是 Location class 没有大洲代码。我希望这能澄清问题,非常感谢您对此的调查!

EDIT2:这是一些示例代码(我有一个抽象的接口而不是接口 class):

abstract class Location {
  protected String name;
  protected double lat;
  protected double lng;

  @Override
  public boolean equals(Object loc) {
    if(!(loc instanceof Location)) {
      return false;
    }
    Location l = (Location) loc;
    return this.lat==l.lat && this.lng==l.lng;
  }

  abstract boolean isContainedIn(Location loc);

} 

class Continent extends Location {
  protected String continentCode;

  @Override
  public boolean equals(Object loc) {
    if(!(loc instanceof Continent)) {
      return false;
    }
    Location l = (Continent) loc;
    return this.continentCode.equals(loc.continentCode);
  }

  boolean isContainedIn(Location loc) {
    if(loc instance of Continent) {
      return false;
    }
     //the following is the logic but won't work since location has no 
       continentCode variable
     //return loc.continentCode.equals(continentCode);
  }
}

class Country extends Location {
  protected String continentCode;
  protected String countryCode;
  @Override
  public boolean equals(Object loc) {
    if(!(loc instanceof Country)) {
      return false;
    }
    Location l = (Country) loc;
    return this.continentCode.equals(loc.continentCode) && this.countryCode.equals(loc.countryCode);
  }

  boolean isContainedIn(Location loc) {
    if(loc instance of Continent|| loc instance of Country) {
      return false;
    }
     //the following is the logic but won't work since location has no 
       countryCode or continent code variable
     //return loc.continentCode.equals(continentCode) && loc.countryCode.equals(countryCode);
  }
}

//其他class会类似

这就是客户端 class 的样子

class ClientClass {
  void someMethod {
   Location l1 = someClass.getLocation(String...searchParameters);
  Location l2 = someClass.getLocation(String...searchParameters);
  if(l1.isContainedIn(l2)) {
    //do something
  }
}

Objective:对位置建模classes,让客户端代码在不知道位置具体类型的情况下使用isContainedIn方法。如果 parent class 不知道大陆代码、国家代码等,可以这样做吗?

备用class设计

//for locations contained in counties
class Location {
  String name;
  double lat;
  double lng;
  String continentCode;
  String countryCode;
  String adm1code;
  String adm2code;
}

class Continent extends Location {
  String countryCode ="";
  String adm1code = "";
  String adm2code = "";

  public Continent(String continentCode) {
    this.continentCode = continentCode;
  }
  //all logic will work but does that violate design principles since Continent technically has no country code, adm1code or adm2code - blank strings returned for these cases?

谢谢。

您陷入了 classic OOP 陷阱,认为仅仅因为两个事物代表相同的概念,这里是地球表面的一个位置,它们应该共享一个基础 class .

在问题的开头,你说你想要他们 "all modeled as a single type"。你选择的单一类型,单点Location没有意义,导致你在代码中询问:location1.contains(location2)。一个点如何包含另一个点?此外,您不使用 lat/long 来决定一个位置是否包含另一个位置,因此这是一个转移注意力的问题。如果您出于其他原因要添加位置,它应该是项目的 属性,而不是基础 class。

所以,我知道这违背了你的全部前提,但我将提出另一个解决方案。

interface ContinentEntity {
    Continent getContinent();
}

// Logically anything that is of a country is also of a continent 
interface CountryEntity extends ContinentEntity {
    Country getCountry();

    // We can satisfy this here in Java 8
    default Continent getContinent() {
        return getCountry().getContinent();
    }
}

public final class Continent {
    private final String continentCode;

    public Continent(String continentCode) {
        this.continentCode = continentCode;
    }

    // As long as the entity reports the same continent, we are good
    // I still don't know whether it's a City or country etc, so it
    // ticks the box of being type agnostic at this point.
    public boolean contains(ContinentEntity continentEntity) {
        return this.equals(continentEntity.getContinent());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || obj.getClass() != getClass()) return false;

        return ((Continent) obj).continentCode.equals(continentCode);
    }

    @Override
    public int hashCode() {
        return continentCode.hashCode();
    }
}

public final class Country implements ContinentEntity {
    // Could be the code, but consider this over stringly type
    private final Continent continent;
    private final String coutryCode;

    Country(Continent continent, String coutryCode) {
        this.continent = continent;
        this.coutryCode = coutryCode;
    }

    @Override
    public Continent getContinent() {
        return continent;
    }

    public boolean contains(CountryEntity countryEntity) {
        return this.equals(countryEntity.getCountry());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || obj.getClass() != getClass()) return false;

        return ((Country) obj).coutryCode.equals(coutryCode);
    }

    @Override
    public int hashCode() {
        return coutryCode.hashCode();
    }
}

public final class City implements CountryEntity {
    private final Country country;
    private final String name;

    public City(Country country, String name) {
        this.country = country;
        this.name = name;
    }

    @Override
    public Country getCountry() {
        return country;
    }
}

所以你可以定义一个新的class:

public final class Village implements CountryEntity {
    private final Country country;
    private final String name;

    public Village(Country country, String name) {
        this.country = country;
        this.name = name;
    }

    @Override
    public Country getCountry() {
        return country;
    }
}

并且您已经可以测试 country.contains(village)continient.contains(village),而无需修改任何现有的 classes。这是我们设计正确的好兆头。见 OCP.

那么它的用法如何:

Continent europe = new Continent("EUROPE");
Country uk = new Country(europe, "UK");
City london = new City(uk, "London");

// Sensible questions compile:
europe.contains(uk);
uk.contains(london);
europe.contains(london);

// Non-sense does not even compile - yay for typesafety for free:
uk.contains(europe);

这是我的最终代码,以防对某人有所帮助。

public interface GeoEntity {

    Optional<GeoEntity> getParent();

    boolean contains(GeoEntity child);
}


public interface ContinentEntity extends GeoEntity {
    Continent getContinent();

}

public interface CountryEntity extends ContinentEntity {

    Country getCountry();

    default Continent getContinent() {
        return getCountry().getContinent();
    }
}

public class Continent implements GeoEntity {

    private final String continentCode;

    public Continent(String continentCode) {
        this.continentCode = continentCode;
    }

    public boolean contains(ContinentEntity continentEntity) {
        return this.equals(continentEntity.getContinent());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || obj.getClass() != getClass()) return false;

        return ((Continent) obj).continentCode.equals(continentCode);
    }

    @Override
    public int hashCode() {
        return continentCode.hashCode();
    }

    @Override
    public Optional<GeoEntity> getParent() {
        return Optional.empty();
    }

    @Override
    public boolean contains(GeoEntity child) {
        if(!ContinentEntity.class.isAssignableFrom(child.getClass())) { 
            return false;
        } 
        ContinentEntity continentEntity = (ContinentEntity) child;
        return contains(continentEntity);
    }

}


public class Country implements ContinentEntity {
    final Continent continent;
    final String countryCode;

    public Country(String continentCode, String countryCode) {
        this.continent = new Continent(continentCode);
        this.countryCode = countryCode;
    }

    @Override
    public Continent getContinent() {
        return continent;
    }

    public boolean contains(CountryEntity countryEntity) {
        return this.equals(countryEntity.getCountry());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || obj.getClass() != getClass()) return false;

        return ((Country) obj).countryCode.equals(countryCode);
    }

    @Override
    public int hashCode() {
        return countryCode.hashCode();
    }

    @Override
    public Optional<GeoEntity> getParent() {
        return Optional.of(continent);
    }

    @Override
    public boolean contains(GeoEntity child) {
        if(!CountryEntity.class.isAssignableFrom(child.getClass())) {
            return false;
        }
        CountryEntity countryEntity = (CountryEntity) child;
        return contains(countryEntity);
    }

}

使用:

public class Mock {

    public static void main(String...args) {
        GeoEntity geo = new Continent("EU");
        GeoEntity geo2 = new Country("EU", "FR");
        //returns true
        System.out.println(geo.contains(geo2));
        //returns false
        System.out.println(geo2.contains(geo));
    }
}