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
.