I'm trying to learn OOP and OOD principles in correct way. I would like to get some clarification on Liskov substitution principle and their PRE and POST conditions. I have read some topics here, some articles from http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod and other places.
I have written a simple base class and several example sub classes with my assumptions on pre and post conditions for very of them and i would like to know if they are correct.
Comment lines is what i think: is it violates or not PRE and POST conditions.
public abstract class BaseClass
{
public virtual int GetResult(int x, int y)
{
if (x > 10 && y < 20)
{
return y - x;
}
throw new Exception();
}
}
public class LSPExample1 : BaseClass
{
public override int GetResult(int x, int y)
{
// PRE: weakened pre condition is ok
if (x > 10 && y <= 15)
{
// POST: Is it ok? because the available result range is narrowed by y <= 15
return y - x;
}
throw new Exception();
}
}
public class LSPExample2 : BaseClass
{
public override int GetResult(int x, int y)
{
// PRE: Same as base - OK
if (x > 10 && y < 20)
{
// POST: I assume it's bad because of parameters place changed in substraction ?
return x-y;
}
throw new Exception();
}
}
public class LSPExample3 : BaseClass
{
public override int GetResult(int x, int y)
{
// PRE Condition is bad (Strenghtened) because of (X >5) and (Y>20) ?
if (x > 5 && y > 20)
{
// POST condition is ok because base class do substraction which is weaker than multiplication ?
return x * y;
}
throw new Exception();
}
}
I would really appreciate your time
This is one of those wonderful situations where you can actually go to the source. Barbara Liskov's original paper is available and quite accessible and easy to read. http://csnell.net/computerscience/Liskov_subtypes.pdf
All four GetResult methods you show accept two integers as input and either return an integer, or throw an exception. In this respect they all have the same behavior so it could be said that LSP is fully satisfied. Without explicit pre/postconditions or invariants, nothing else can really be said about the code.
What is missing in your example is the contract. What does GetResult require from its caller and what does it guarantee it will produce?
If, for example, the contract guarantees that the return value will equal y - x then Examples 2 and 3 fail the contract and therefore break LSP. If, however, the only guarantee is that the return value will be an Int or Exception, then they all pass.
If the contract guarantees that and exception will be thrown if x <=10 || y >= 20 then Example 1 & 3 break LSP. If the only guarantee is that the method will return an Int or throw an Exception, then they all satisfy it.
The code can't tell you what the guarantees are, it can only tell you want the code does.
Since I'm getting lots of up-votes, I'll add an example (pseudo-code):
class Line {
int start
int end
int length() { return end - start } // ensure: length = end - start
void updateLength(int value) {
end = start + value
// ensure: this.length == value
}
}
The "ensure" clause in each of the two functions are guarantees regarding the state of a Line object after the function is called. As long as I satisfy the guarantees in sub-classes, my sub-class will conform to LSP. So for example:
class Example1: Line {
void updateLength(int value) {
start = end - value
}
}
The above satisfies LSP.
If the Line's updateLength function also has an ensure clause of "this.start unchanged" then my subclass would not satisfy LSP.
Related
fun findError(puzzle: Array<IntArray>): Boolean {
for (z in 0..8) {
val blockNums = mutableListOf<Int>()
val xNums = mutableListOf<Int>()
val yNums = mutableListOf<Int>()
for (index in 0..8) {
xNums.add(puzzle[z][index])
yNums.add(puzzle[index][z])
blockNums.add(puzzle[blocks.xy[z + 1][index]][blocks.xy[z][index]])
if (blockNums.count() != blockNums.toSet().count() ||
yNums.count() != yNums.toSet().count() ||
xNums.count() != xNums.toSet().count()) return false
}
}
return true
}
This function works as desired but the Intellij IDE gives this warning. "Boolean method 'findError' is always inverted". I kind of understand what it means and I know I could suppress it.
I can't figure out how to rewrite the code block to satisfy the error and not change the functionality. Should I just suppress it or is there a more proper way to express this? I'm a beginner that is learning.
public final data class Blocks public constructor(blockNums: kotlin.collections.MutableList<kotlin.Int>, blockNumsFinal: kotlin.collections.MutableSet<kotlin.Int>, xy: kotlin.collections.List<kotlin.collections.List<kotlin.Int>>) {
public final val blockNums: kotlin.collections.MutableList<kotlin.Int> /* compiled code */
public final val blockNumsFinal: kotlin.collections.MutableSet<kotlin.Int> /* compiled code */
public final var xy: kotlin.collections.List<kotlin.collections.List<kotlin.Int>> /* compiled code */
public final operator fun component1(): kotlin.collections.MutableList<kotlin.Int> { /* compiled code */ }
public final operator fun component2(): kotlin.collections.MutableSet<kotlin.Int> { /* compiled code */ }
public final operator fun component3(): kotlin.collections.List<kotlin.collections.List<kotlin.Int>> { /* compiled code */ }
}
I found a way to rewrite the function not using the '!' symbol. This should satisfy the intention of the warning to use positives instead of negatives. It still gives the warning. I think Bas Leijdekkers comment about the inspection may be correct.
fun findError(puzzle: Array<IntArray>): Boolean {
val blockNums = mutableListOf<Int>()
val xNums = mutableListOf<Int>()
val yNums = mutableListOf<Int>()
var counts = 0
for (z in 0..8) {
blockNums.clear()
xNums.clear()
yNums.clear()
for (index in 0..8) {
xNums.add(puzzle[z][index])
yNums.add(puzzle[index][z])
blockNums.add(puzzle[blocks.xy[z + 1][index]][blocks.xy[z][index]])
if (blockNums.count() == blockNums.toSet().count() &&
yNums.count() == yNums.toSet().count() &&
xNums.count() == xNums.toSet().count()) {
counts++
}
}
}
return counts == 81
}
Which line is the IntelliJ warning you about, is it this statement?
if (blockNums.count() != blockNums.toSet().count() ||
yNums.count() != yNums.toSet().count() ||
xNums.count() != xNums.toSet().count()) return false
if so, it is because this is complex and likely difficult for someone other than you to understand. Here's two ideas about how you might make reduce the complexity:
(1)
if (blockNums.size != blockNums.toSet().size) return false
if (yNums.size != yNums.toSet().size) return false
if (xNums.size != xNums.toSet().size) return false
or (2)
val blocksDiffer = (blockNums.size != blockNums.toSet().size)
val yDiffer = (yNums.size != yNums.toSet().size)
val xDiffer = (xNums.size != xNums.toSet().size)
if(blocksDiffer || yDiffer || xDiffer) return false
(There is a small performance penalty with (2) since the program has to compute all 3 evaluations.
I also changed count() to size which are equivalent)
I believe this is IntelliJ trying to warn you that a boolean method result is always inverted after calling. In other words, you only ever use !findError() in your code.
This is an indication that the code could be made more readable by using positive language. JetBrain's justification for this appears to be based on Robert Martin's book Clean Code:
“Negatives are just a bit harder to understand than positives. So, when possible, conditionals should be expressed as positives.”
You can use Refactor -> Invert Boolean... to perform this automatically.
I'm attempting to implement a class that 'does' Positional that also allows me to update its values by assigning to the result returned by the AT-POS method. Eventually, I was able to concoct the following class that works as intended:
class Test does Positional
{
has $.slot_1 is rw = 12;
has $.slot_2 is rw = 24;
method AT-POS(\position)
{
my $t = self;
return-rw Proxy.new:
FETCH => method ()
{
position % 2 ?? $t.slot_1 !! $t.slot_2
},
STORE => method ($v)
{
if position % 2
{
$t.slot_1 = $v
}
else
{
$t.slot_2 = $v
}
}
}
}
my $test = Test.new;
die unless $test[2] == 24;
die unless $test[5] == 12;
$test[7] = 120;
die unless $test[2] == 24;
die unless $test[5] == 120;
$test[10] = 240;
die unless $test[2] == 240;
die unless $test[5] == 120;
Would it be possible to somehow (and: simply) return the container bound to $!slot_1 (or $!slot_2) inside the Test class implementation?
Before I discovered the use of Proxy instances I attempted to return (and return-rw) the result of expression position % 2 ?? $!slot_1.VAR !! $!slot_2.VAR, because I'm under the impression that the VAR method gives me access to the underlying container, in the hope that I can simply return it. That didn't really work, and I do not understand why yet: I suspect it somehow gets coerced back to a value somehow?
So in other words: is it possible to simplify my AT-POS implementation in this particular situation?
Thanks,
Regards,
Raymond.
Assuming you do not want accessors for "slot_1" and "slot_2", and if I understand the question correctly, this would be my implementation. I wouldn't call it a Test class, as that would interfere with the Test class that is used for testing.
class Foo {
has #elements = 24, 12;
method AT-POS(Int:D $pos) is raw {
#elements[$pos % 2]
}
}
my $f = Foo.new;
say $f[2]; # 24
say $f[5]; # 12
$f[2] = 666;
say $f[4]; # 666
Note that the defaults in the array have changed order, that's to keep the arithmetic in AT-POS simple.
Also note the is raw in the definition of the AT-POS method: it will ensure that no de-containerization will take place when returning a value. This allows you to just assign to whatever $f[2] returns.
Hope this made sense!
Also: the Array::Agnostic module may be of interest for you, to use directly, or to use as a source of inspiration.
First off if you aren't going to use an attribute outside of the object, there isn't a reason to declare them as public, and especially not rw.
class Foo {
has $!odd = 12;
has $!even = 24;
…
}
You can also directly return a Scalar container from a method. You should declare the method as rw or raw. (raw doesn't guarantee that it is writable.)
class Foo {
has $!odd = 12;
has $!even = 24;
method AT-POS(\position) is rw {
position % 2 ?? $!odd !! $!even
}
}
# we actually get the Scalar container given to us
say Foo.new[10].VAR.name; # $!even
Note that even if you declare the attributes as public they still have a private name. The private attribute is always rw even if it isn't publicly declared as rw.
class Foo {
has $.odd = 12;
has $.even = 24;
method AT-POS(\position) is rw {
position % 2 ?? $!odd !! $!even
}
}
If you are going to use a Proxy, I would consider moving the common code outside of it.
class Foo {
has $.odd = 12;
has $.even = 24;
method AT-POS(\position) is rw {
# no need to write this twice
my $alias := (position % 2 ?? $!odd !! $!even);
Proxy.new:
FETCH => method () { $alias },
STORE => method ($new-value) { $alias = $new-value }
}
}
Of course the ?? !! code is a core feature of this module, so it would make sense to put it into a single method so that you don't end up with duplicate code all over your class. In this case I made it a private method.
class Foo {
has $.odd = 12;
has $.even = 24;
# has to be either `raw` or `rw`
# it is debatable of which is better here
method !attr(\position) is raw {
position % 2 ?? $!odd !! $!even
}
method AT-POS(\position) is rw {
my $alias := self!attr(position);
Proxy.new:
FETCH => -> $ { $alias },
STORE => -> $, $new-value { $alias = $new-value }
}
}
Again, not much reason to use a Proxy.
class Foo {
has $.odd = 12;
has $.even = 24;
method !attr(\position) is raw {
position % 2 ?? $!odd !! $!even
}
method AT-POS(\position) is rw {
self!attr(position);
}
}
Instead of ?? !! you could use an indexing operation.
method !attr(\position) is raw {
($!even,$!odd)[position % 2]
}
Which would allow for a ternary data structure.
method !attr(\position) is raw {
($!mod0,$!mod1,$!mod2)[position % 3]
}
There was no need to write the if statement that you did as Raku usually passes Scalar containers around instead of the value.
(position % 2 ?? $t.slot_1 !! $t.slot_2) = $v;
to get familiar with optaplanner i created a simple test project. I only have one Solution and one Entity class. The Entity has only one value between 0 and 9. There should only be odd numbers and the sum of all should be less then 10 (this are just some random constraints i came up with).
As Score i use a simple HardSoftScore. Here is the code:
public class TestScoreCalculator implements EasyScoreCalculator<TestSolution>{
#Override
public HardSoftScore calculateScore(TestSolution sol) {
int hardScore = 0;
int softScore = 0;
int valueSum = 0;
for (TestEntity entity : sol.getTestEntities()) {
valueSum += entity.getValue() == null? 0 : entity.getValue();
}
// hard Score
for (TestEntity entity : sol.getTestEntities()) {
if(entity.getValue() == null || entity.getValue() % 2 == 0)
hardScore -= 1; // constraint: only odd numbers
}
if(valueSum > 10)
hardScore -= 2; // constraint: sum should be less than 11
// soft Score
softScore = valueSum; // maximize
return HardSoftScore.valueOf(hardScore, softScore);
}
}
and this is my config file:
<?xml version="1.0" encoding="UTF-8"?>
<solver>
<!-- Domain model configuration -->
<scanAnnotatedClasses/>
<!-- Score configuration -->
<scoreDirectorFactory>
<easyScoreCalculatorClass>score.TestScoreCalculator</easyScoreCalculatorClass>
</scoreDirectorFactory>
<!-- Optimization algorithms configuration -->
<termination>
<secondsSpentLimit>30</secondsSpentLimit>
</termination>
</solver>
for some reason OptaPlanner cant find a feasible solution. It terminates with LS step (161217), time spent (29910), score (-2hard/10soft), best score (-2hard/10soft)... and the solution 9 1 0 0.
So the hardScore is -2 because the two 0 are not odd. A possible solution would be 7 1 1 1 for example. Why is this ? This should be a really easy example ...
(when i set the Start values to 7 1 1 1 it terminates with this solution and a score of (0hard/10soft) how it should be)
Edit:
The Entity class
#PlanningEntity
public class TestEntity {
private Integer value;
#PlanningVariable(valueRangeProviderRefs = {"TestEntityValueRange"})
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
#ValueRangeProvider(id = "TestEntityValueRange")
public CountableValueRange<Integer> getStartPeriodRange() {
return ValueRangeFactory.createIntValueRange(0, 10);
}
}
The Solution class
#PlanningSolution
public class TestSolution {
private List<TestEntity> TestEntities;
private HardSoftScore score;
#PlanningEntityCollectionProperty
public List<TestEntity> getTestEntities() {
return TestEntities;
}
public void setTestEntities(List<TestEntity> testEntities) {
TestEntities = testEntities;
}
#PlanningScore
public HardSoftScore getScore() {
return score;
}
public void setScore(HardSoftScore score) {
this.score = score;
}
#Override
public String toString() {
String str = "";
for (TestEntity testEntity : TestEntities)
str += testEntity.getValue()+" ";
return str;
}
}
The Main Program class
public class Main {
public static final String SOLVER_CONFIG = "score/TestConfig.xml";
public static int printCount = 0;
public static void main(String[] args) {
init();
}
private static void init() {
SolverFactory<TestSolution> solverFactory = SolverFactory.createFromXmlResource(SOLVER_CONFIG);
Solver<TestSolution> solver = solverFactory.buildSolver();
TestSolution model = new TestSolution();
List<TestEntity> list = new ArrayList<TestEntity>();
// list.add(new TestEntity(){{setValue(7);}});
// list.add(new TestEntity(){{setValue(1);}});
// list.add(new TestEntity(){{setValue(1);}});
// list.add(new TestEntity(){{setValue(1);}});
for (int i = 0; i < 4; i++) {
list.add(new TestEntity());
}
model.setTestEntities(list);
// Solve the problem
TestSolution solution = solver.solve(model);
// Display the result
System.out.println(solution);
}
}
It gets stuck in a local optima because there is no move that takes 1 from entity and gives it to another entity. With a custom move you can add that.
These kind of moves only apply to numeric value ranges (which are rare, usually value ranges are a list of employees etc), but they should probably exist out of the box (feel free to create a jira for them).
Anyway, another way to get the good solution is to add <exhaustiveSearch/>, that bypassing local search and therefore the local optima. But that doesn't scale well.
I have two bits of code
Tree tree;
void setup() {
int SZ = 512; // screen size
int d = 2;
int x = SZ/2;
int y = SZ;
size(SZ,SZ);
background(255);
noLoop();
tree = new Tree(d, x, y);
}
void draw() {
tree.draw();
}
and also
class Tree {
// member variables
int m_lineLength; // turtle line length
int m_x; // initial x position
int m_y; // initial y position
float m_branchAngle; // turtle rotation at branch
float m_initOrientation; // initial orientation
String m_state; // initial state
float m_scaleFactor; // branch scale factor
String m_F_rule; // F-rule substitution
String m_H_rule; // H-rule substitution
String m_f_rule; // f-rule substitution
int m_numIterations; // number of times to substitute
// constructor
// (d = line length, x & y = start position of drawing)
Tree(int d, int x, int y) {
m_lineLength = d;
m_x = x;
m_y = y;
m_branchAngle = (25.7/180.0)*PI;
m_initOrientation = -HALF_PI;
m_scaleFactor = 1;
m_state = "F";
m_F_rule = "F[+F]F[-F]F";
m_H_rule = "";
m_f_rule = "";
m_numIterations = 5;
// Perform L rounds of substitutions on the initial state
for (int k=0; k < m_numIterations; k++) {
m_state = substitute(m_state);
}
}
void draw() {
pushMatrix();
pushStyle();
stroke(0);
translate(m_x, m_y); // initial position
rotate(m_initOrientation); // initial rotation
// now walk along the state string, executing the
// corresponding turtle command for each character
for (int i=0; i < m_state.length(); i++) {
turtle(m_state.charAt(i));
}
popStyle();
popMatrix();
}
// Turtle command definitions for each character in our alphabet
void turtle(char c) {
switch(c) {
case 'F': // drop through to next case
case 'H':
line(0, 0, m_lineLength, 0);
translate(m_lineLength, 0);
break;
case 'f':
translate(m_lineLength, 0);
break;
case 's':
scale(m_scaleFactor);
break;
case '-':
rotate(m_branchAngle);
break;
case '+':
rotate(-m_branchAngle);
break;
case '[':
pushMatrix();
break;
case ']':
popMatrix();
break;
default:
println("Bad character: " + c);
exit();
}
}
// apply substitution rules to string s and return the resulting string
String substitute(String s) {
String newState = new String();
for (int j=0; j < s.length(); j++) {
switch (s.charAt(j)) {
case 'F':
newState += m_F_rule;
break;
case 'H':
newState += m_F_rule;
break;
case 'f':
newState += m_f_rule;
break;
default:
newState += s.charAt(j);
}
}
return newState;
}
}
This isn't assessed homework, it's an end of chapter exercise but I'm very stuck.
I want to "extend the Tree constructor so that values for all of the Tree member variables can be passed in as parameters."
Whilst I understand what variables and parameters are, I'm very stuck as to what to begin reading / where to begin editing the code.
One thing that has confused me and made me question my understanding is that, if I change the constructor values, (for example m_numiterations = 10;), the output when the code is run is the same.
Any pointers in the right direction would be greatly appreciated.
You already have everything in there to add more stuff to your Tree.
You see, in your setup(), you call:
tree = new Tree(d, x, y);
Now, that line, is actually calling the contructor implemented here:
Tree(int d, int x, int y) {
m_lineLength = d;
m_x = x;
etc....
So, if you want you can change that constructor to accept any variable that you want to pass from setup()
For instance, Tree(int d, int x, int y, String word, float number, double bigNumber)
Try experimenting with that. If you have any questions, PM me
EDIT
Let me add a little more flavor to it:
You see constructors are the way to initialize your class. It does not matter the access level (protected, public, private) or the number of constructors.
So, for example, Let's say you have this class with two public fields:
public class Book
{
public String Author;
public String Title;
public Book(String title, String author)
{
this.Title = title;
this.Author = author;
}
public Book()
{
this("Any title");//default title
}
}
Here, you can create books with both author and title OR only title! isn't that great? You can create things that are not inclusively attached to other things!
I hope you understand this. But, basically the idea is to encapsulate everything that matters to a certain topic to its own class.
NEW EDIT
Mike, you see, according to your comment you added this line:
int m_numIterations = 25;
The thing is that what you just did was only create a variable. A variable holds the information that you eventually want to use in the program. Let's say you are in high school physics trying to solve a basic free fall problem. You have to state the gravity, don't you?
So, in your notebook, you would go:
g = 9.8 m/s^2
right? it is a constant. But, a variable that you will use in your problem.
Well, the same thing applies in programming.
You added the line. That means that now, you can use it in your problem.
Now, go to this line,
tree = new Tree(d, x, y);
and change it to:
tree = new Tree(d, x, y, m_numIterations);
As you can see, now you are ready to "use" your variable in your tree. However! you are not done yet. You have to update as well your constructor because if not, the compiler will complain!
Go to this line now,
Tree(int d, int x, int y) {
m_lineLength = d;
m_x = x;
....
And change it to:
Tree(int d, int x, int y, int iterations) {
m_lineLength = d;
m_x = x;
....
You, see, now, you are telling your tree to accept a new variable call iterations that you are setting from somewhere else.
However! Be warned! There is a little problem with this :(
You don't have any code regarding the use of that variable. So, if you are expecting to actually see something different in the Tree, it won't happen! You need to find a use to the variable within the scope of the Tree (the one that I called iterations). So, first, find a use for it! or post any more code that you have to help you solve it. If you are calling a variable iterations, it is because you are planning to use a loop somewhere, amirite? Take care man. Little steps. Be patient. I added a little more to the Books example. I forgot to explain it yesterday :p
The isSorted() instance method in class A has a bug:
public class A {
private int[] a;
public A(int[] a) { this.a = a; }
/** Return true if this A object contains an array sorted
* in nondecreasing order; else false. */
public boolean isSorted() {
for(int i=1; i<a.length-1; i++) {
if(a[i] < a[i-1]) return false;
}
return true;
}
}
Write a JUnit test method testIsSorted() which will fail because of this bug, but will pass when the bug is fixed.
(Assume that there is no setUp() method defined.)
This is the answer:
public void testIsSorted() {
int[] array = {2, 1};
A haha = new A(array);
assertFalse(haha.isSorted);
}
first of all where is the bug, i cannot seem to located it.
Secondly shoudn't it be assertTrue(haha.isSorted)
because when its assertFalse it will pass because the array is in descending order, therefore the isSorted will return false and assertFalse(false) will return true where-as assertTrue(false) will return false.
The bug is on the line
for(int i=1; i<a.length-1; i++) {
Since array indexes start at 0, the definition of i should be int i=0, not 1. The index 1 points to the second element of the array.
The assertFalse statement checks that the isSorted() method returns false for the given array {2,1}. The isSorted() method checks that no entry is less than the previous one (conversely, each entry is greater than or equal to the previous one). In the example, it will return false, because 2 at index 0 is greater than 1 at index 1. Therefore, the assertFalse is the correct assertion for the case.
You could also test like this (note the reversed order of array).
public void testIsSorted() {
int[] array = {1, 2};
A haha = new A(array);
assertTrue(haha.isSorted());
}