Ho una classe Party con un costruttore che accetta Collection<Foo> . Ho in programma di avere due sottoclassi NpcParty e PlayerParty . Tutte le istanze di Party hanno un limite superiore per la dimensione della collezione di input (6). Tuttavia, un NpcParty ha un limite inferiore di 1 , mentre un PlayerParty ha un limite inferiore di 0 (o piuttosto, vi è un limite minimo implicito perché un List non può avere una dimensione negativa). Questa è una violazione di LSP perché NpcParty rafforza le precondizioni in termini di dimensioni della collezione di input.
Un NpcParty è inteso essere immutabile; il che significa che l'unico Foo s che avrà mai, sono quelli che sono specificati nel costruttore. La PlayerParty s di Foo può essere modificata in fase di runtime, sia in ordine, sia come riferimento / valore.
public class Party {
// collection must not be null
// collection size must not exceed PARTY_LIMIT
public Party(List<Foo> foos) {
Objects.requireNonNull(foos);
if (foos.size() > PARTY_LIMIT) {
throw new IllegalArgumentException("foos exceeds party limit of " + PARTY_LIMIT);
}
}
}
public class NpcParty extends Party {
// additional precondition that there must be at least 1 Foo in the collection
public NpcParty(List<Foo> foos) {
super(foos);
if (foos.size() < 1) {
throw new IllegalArgumentException("foos must contain at least 1 Foo");
}
}
}
public class PlayerParty extends Party {
// no additional preconditions
public PlayerParty(List<Foo> foos) {
super(foos);
}
}
In che modo posso risolvere questa violazione, in modo che un NpcParty possa avere un limite minimo?
EDIT : darò un esempio di come potrei testarlo.
Supponiamo di avere una classe AbstractPartyUnitTests che ha testato il contratto minimo di tutte le implementazioni Party .
public class AbstractPartyUnitTests {
@Test(expected = NullPointerException.class)
public void testNullConstructor() {
createParty(null);
}
@Test
public void testConstructorWithEmptyList() {
party = createParty(new ArrayList<Foo>());
assertTrue(party != null);
}
@Test(expected = IllegalArgumentException.class)
public void testConstructorThatExceedsMaximumSize() {
party = createParty(Stream.generate(Foo::new)
.limit(PARTY_LIMIT + 1)
.collect(Collectors.toList()));
}
protected abstract Party createParty(List<Foo> foos);
private Party party;
}
Con sottoclassi per PlayerParty e NpcParty
public class PlayerPartyUnitTests extends AbstractPartyUnitTests {
@Override
protected Party createParty(List<Foo> foos) {
return new PlayerParty(foos);
}
}
e
public class NpcPartyUnitTests extends AbstractPartyUnitTests {
@Test
public void testConstructorThatMeetsMinimumSize() {
party = createParty(Stream.generate(Foo::new)
.limit(1)
.collect(Collectors.toList());
assertTrue(party != null);
}
@Test(expected = IllegalArgumentException.class)
public void testConstructorThatDoesNotMeetMinimumSize() {
party = createParty(new ArrayList<Foo>());
}
@Override
protected Party createParty(List<Foo> foos) {
return new NpcParty(foos);
}
}
Tutti i casi di test passeranno, ad eccezione di testConstructorWithEmptyList durante l'esecuzione come NpcPartyUnitTests . Il costruttore fallirebbe e, come tale, il test fallirebbe.
Ora, potrei rimuovere quel test dalla classe AbstractPartyUnitTests , in quanto non si applica a tutti i tipi di Party ; ma poi ovunque io abbia un Party , potrei non essere in grado di avere una sostituzione 1: 1 di NpcParty .