将继承与 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));
}
}
我正在尝试在 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));
}
}