• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

basic terms

 
Ranch Hand
Posts: 81
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi,

I have a doubt on basic terms of good OO designs.
What does flexible means? Does it mean easy to change?
Is it any different from maintainable or extensible?
These words often come together, do they mean different concepts worth distinguishing?

I am reading head first OO and missed the concept beyond flexible. The book says "bla bla bla flexible and easy to change". Is the phrase "easy to change" redundant in the quoted sentence?

Thanks
[ March 05, 2007: Message edited by: Jasiek Motyka ]
 
(instanceof Sidekick)
Posts: 8791
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I'd say all those words are talking about subtly different aspects of the very same thing. To add more terms to the concept, we practice "dependency management" to achieve "low coupling" so we can change one part of the system without breaking other parts.

Is "flexible and easy to change" redundant? Maybe. They may mean to say changing this design goes beyond "possible" all the way to "easy". I'd have no complaint about the langauge except now you tell us it might be read to mean flexible and easy are two different things. I have tremendous respect for anyone who can function in a second language (or third or fourth) but it's impossible to imagine how every reader will approach something. Keep asking when you find confusing bits.

BTW: It's very cool that you're into Head First OO and this aspect of programming. Hang out in this forum and we'll have you slinging jargon in no time.
 
author
Posts: 9050
21
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hey Jasiek,

I agree with Stan - some of JavaRanch's guru-iest gurus hang out in this forum

Bert
 
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
In some sense it is easier to talk about what we *don't* want a design to be like (I blatantly stole this notion from Robert C. Martin).

A design is *not* flexible if it is

- rigid: to apply a single change, we have to change many other places, too

- fragile: if we apply a change, the system often breaks in unexpected ways

- immobile: it is hard to reuse parts of the design in other places

- viscous: it is easier to do dirty hacks and workarounds than doing "the right thing" (TM)

See http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf for a good, deeper discussion.
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
To me, "flexible" is a stronger word than "extensible". In some sense, a design could be rather inflexible on how it can be extended.

The notion of "maintainability" to me adds the question on how easy it is to understand the design, and how easy it is to find out whether we broke something by a change we applied.

That's just my personal understanding, though - I doubt that there is a single, commonly agreed upon definition of those terms.
 
Ranch Hand
Posts: 1170
Hibernate Eclipse IDE Ubuntu
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Flexible.equals("easy to change")

maintainable means that its easy to fix bugs you find.

extensible means it can be made to do more things by adding onto it. As opposed to by changing it too much.

flexible, maintainable, and extensible mean differen things. They all have to do with how easy it is to modify the behavior of the program.
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Mr. C Lamont Gilbert:

maintainable means that its easy to fix bugs you find.



I can understand why you would use that definition.

Most often, though, in software development it is meant to also include feature enhancements: http://www.google.com/search?q=define:+maintenance&defl=en
 
Stan James
(instanceof Sidekick)
Posts: 8791
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Then we'd have to argue whether "maintenance" was a valid word for software. You maintain trucks because the oil gets dirty and parts wear out, but software? What wears out? In our world "maintenance" is just working on something that is not a fun new system, tending towards life support of a comatose patient who doesn't do much.

Someplace I found a list of "Seven Deadly Smells" attributed to Uncle Bob: Complexity, Fragility, Rigidity, Repetition, Imobility, Opacity and Viscosity.

I love viscosity - easier to do the wrong thing than the right thing. Ever see a grownup race a little child across the swimming pool? The kid gets out of the water and runs around. I've worked in so many frameworks that were major impediments to getting anything done! I really wanted to climb out of the water and run. Away.
 
Marshal
Posts: 28193
95
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
What wears out?

Well, it's like this. The software was written to satisfy certain requirements. (Let's take a big leap of faith and suppose that it did in fact satisfy those requirements.) But then the requirements change. The state puts a new tax on motor oil. The SEC requires 47 new reports. You want to interface with Fedex to find out where your shipments are. You name it.

Now the nice round peg doesn't fit in the nice round hole any more because the hole isn't the same shape. So it starts squeaking and binding and it needs to be worked on. So it isn't the software that wears out, it's the relationship between the software and its environment that wears out.
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Paul, very well put!
 
Stan James
(instanceof Sidekick)
Posts: 8791
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Cute I have no problem with "maintenance" but it's an invitation to flame wars in some circles. Changes in requirements might just sound like more stories. Do they really need a special name?
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Stan James:
Changes in requirements might just sound like more stories. Do they really need a special name?



Ron Jeffries is known for saying that an Agile team is in maintenance mode since iteration 2...
 
Ranch Hand
Posts: 131
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
When you have good tools that don't make mistakes, like IDEA and Eclipse, then it is easy to change code when new requirements come in.

When you don't have that, say, because you're using a dynamically typed language (without Bracha's optional typing), then it is not easy to change code when new requirements come in.

Therefore, the importance of ease-of-change versus ease-of-reuse is skewed depending on your language and tools (and coding style; in Java if you use reflection a lot the tools are less useful).

Generally speaking, design for reuse, but don't give names to things that aren't used at least twice, unless using them anonymously would result in hard-to-read code, and don't introduce indirection unless it's useful.

Interestingly, languages that let you change what a method/constructor does at runtime don't need the factory antipattern so much; it's already been subsumed into the language.

I'm saying antipattern on purpose, as I believe patterns are a halfway step towards a truly reusable solution with no repeated code. The name 'pattern' lets you be happy with the halfway step, when you shouldn't be.

It might be beyond your skills/permissions to integrate the pattern into the language (such as Java 5 did with the typesafe enum pattern), but it's always worth recognising that there's something missing from your skills/language if you're writing in patterns.
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ricky Clarkson:
When you have good tools that don't make mistakes, like IDEA and Eclipse, then it is easy to change code when new requirements come in.

When you don't have that, say, because you're using a dynamically typed language (without Bracha's optional typing), then it is not easy to change code when new requirements come in.



I can tell you for sure that even a tool like IDEA or Eclipse can't prevent you from introducing bugs when changing software due to new requirements.

What's absolutely essential to keep software flexible is an extensive set of both automated unit- and acceptance tests. And those work as well for dynamically typed languages as for statically typed ones.

It might be beyond your skills/permissions to integrate the pattern into the language (such as Java 5 did with the typesafe enum pattern), but it's always worth recognising that there's something missing from your skills/language if you're writing in patterns.



Interesting statement. What is missing from all those OO languages that we use the Strategy pattern so often, for example?
 
Ricky Clarkson
Ranch Hand
Posts: 131
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I only said that the tools don't make mistakes, not that the users don't.

The strategy pattern would better be done by adding a method to the class, or to use the opposite, but equivalent, technique, by adding a generic method that can operate on instances of the class.

The latter is explained here: http://anthonyf.wordpress.com/2006/04/07/strategy-pattern-in-lisp/
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ricky Clarkson:
I only said that the tools don't make mistakes, not that the users don't.



You also said that "it is easy to change code when new requirements come in" using those tools. I'm just saying that in my experience the more important factor is a good test coverage. A good refactoring tool is nice, but not necessary - let alone static typing.


The strategy pattern would better be done by adding a method to the class, or to use the opposite, but equivalent, technique, by adding a generic method that can operate on instances of the class.

The latter is explained here: http://anthonyf.wordpress.com/2006/04/07/strategy-pattern-in-lisp/



I will take a look.

Can you shortly explain what exactly "would be better" means to you? That is, what persuasive advantages do you see? Thanks!
 
Ricky Clarkson
Ranch Hand
Posts: 131
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

You also said that "it is easy to change code when new requirements come in" using those tools. I'm just saying that in my experience the more important factor is a good test coverage. A good refactoring tool is nice, but not necessary - let alone static typing.



Given that changing, say, the name of a method, has O(n) time, where n is the number of uses of that method, it is harder to change code that has high test coverage than code that doesn't.

Good tools make changing the name of a method have O(1) time, at least until n is 10000 or so, I'd imagine.

Certainly, however, good tests make verifying the correctness of the change easier, but they don't actually make the editing tasks easier.

If you have reliable refactoring tools, and a style of code that fits them, i.e., no reflection, then you can know that the refactor hasn't broken anything. The only time I break code when refactoring is when I'm doing an unsupported refactor, i.e., I'm doing it by hand.

Static typing can be viewed as just another part of your automated testing. The only thing special about it is that most implementations of it are mandatory (programs won't run if the static tests fail). In that respect, given strong enough static typing, you need less tests. In a language where a method parameter cannot be null, you don't need to test for what happens if you pass null to the method. In a language where method parameters are typed, you don't need to test what happens if you pass an int to a method that expects a String.

In Lisp, you get a Turing-complete environment at compile-time too, so you could, in theory at least, do all your automated tests as part of compilation - in that case there would be no difference between static typing (well, static testing) and automated testing. Hence, I think that automated testing and static typing are just two flavours of the same medicine.

Haskell programmers aim for complete static testing - by making their type system as strong as possible, they can prove things without having to write dull, repetitive tests. The awkward part is that the static tests in Haskell are mandatory; you can't run a program whose types don't quite fit together. I don't think Haskell's type system is quite Turing-complete though, and it certainly has holes (bottom, unsafePerformIO).

Can you shortly explain what exactly "would be better" means to you?



Sure. Take some code, which has some repeating portions. Change it in some way so that it does the same thing, without the repeating portions. It is now better.

This is not universally true; for example, if you have repetition in the requirements then you should have repetition in the code, to make the solution as close to the problem as possible. The best solution there, in my opinion, is to work with the requirer to remove the repetition from the requirements. I appreciate that this is not always practical.
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ricky Clarkson:
Given that changing, say, the name of a method, has O(n) time, where n is the number of uses of that method, it is harder to change code that has high test coverage than code that doesn't.

Good tools make changing the name of a method have O(1) time, at least until n is 10000 or so, I'd imagine.

Certainly, however, good tests make verifying the correctness of the change easier, but they don't actually make the editing tasks easier.



They don't make the *editing* easier, but they *do* make *the whole change* easier - assuming we actually care that the change didn't brake anything.

If you have reliable refactoring tools, and a style of code that fits them, i.e., no reflection, then you can know that the refactor hasn't broken anything.



You can also be sure that you didn't implement the new feature, because a refactoring - by definition - doesn't change the behaviour of the system. So there certainly must be more to it than refactoring - and that's the point when the tests enter the stage.

As an aside, the very first refactoring browser was implemented for Smalltalk, a dynamically typed language.

In that respect, given strong enough static typing, you need less tests.



How much experience do you have with testing in dynamically typed languages? I have yet to meet someone fluent in both styles who writes more tests in dynamically typed languages than in statically typed ones.


In a language where a method parameter cannot be null, you don't need to test for what happens if you pass null to the method. In a language where method parameters are typed, you don't need to test what happens if you pass an int to a method that expects a String.



Even in a language where a method parameter *can* be null, I don't test for what happens when I pass null when the method doesn't have to work with null arguments. Why would I? Why should I test that a method barfs when it gets an illegal argument?

In Lisp, you get a Turing-complete environment at compile-time too, so you could, in theory at least, do all your automated tests as part of compilation - in that case there would be no difference between static typing (well, static testing) and automated testing. Hence, I think that automated testing and static typing are just two flavours of the same medicine.



That's an interesting hypothesis, and perhaps it's true for Lisp. For Java, I don't see how the type system makes a significant difference on what tests I need to write - perhaps the type system is just so weak, I don't know.

Haskell programmers aim for complete static testing - by making their type system as strong as possible, they can prove things without having to write dull, repetitive tests.



If your tests are dull and/or repetitive, you can improve without using a "more static" type system.

And I don't see how a type system could totally replace tests at all. The whole point of tests is that they work like double entry booking - you express the same thing in two quite different ways, tests and production code, and if the two agree, you can be quite confident that you didn't do a silly mistake.

Sure. Take some code, which has some repeating portions. Change it in some way so that it does the same thing, without the repeating portions. It is now better.



Sure - Don't Repeat Yourself. One of my favorites.

Now how does that connect to the Strategy pattern?
 
Stan James
(instanceof Sidekick)
Posts: 8791
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I followed that Strategy in Lisp link and it didn't seem to do what I want from Strategy. Just because factory seemed to factor in the discussion, let's look at strategy without a factory, say for some competitive endeavor ...

Lisp does things very differently. It may do them far better. I don't care because I don't get paid to use it, and I've given up tyring to imitate one language in another. So maybe this had no point at all. Sigh.
 
Bartender
Posts: 2968
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Stan James:

Someplace I found a list of "Seven Deadly Smells" attributed to Uncle Bob: Complexity, Fragility, Rigidity, Repetition, Imobility, Opacity and Viscosity.



Robert C. Martin: Agile Software Development, Principles, Patterns, and Practices amazon US
Chapter 7: Design Smells - The Odors of Rotting Software (p.88-89)
See also: AdoptionSoft: Software Craftsmanship

In General: Bad Smells in Code
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ricky Clarkson:
The strategy pattern would better be done by adding a method to the class, or to use the opposite, but equivalent, technique, by adding a generic method that can operate on instances of the class.



By "adding a method to the class", are you talking about something like the Template Method pattern?


The latter is explained here: http://anthonyf.wordpress.com/2006/04/07/strategy-pattern-in-lisp/



Unfortunately, my understanding of Lisp isn't good enough to actually understand that example.

One advantage of the Strategy pattern is that you can pass in a Strategy that wasn't known at compile time of the original code. Just write a class that implements the correct interface.

What had you do in the Lisp example when you want to introduce another Strategy instance?
 
Ricky Clarkson
Ranch Hand
Posts: 131
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

By "adding a method to the class", are you talking about something like the Template Method pattern?



No. Imagine that you could, with Java, at any time, add a method to a class. Example syntax:



Now, "aBCd".numLowerCase() returns 2.

Ruby and JavaScript allow this. Common Lisp does it in a reverse way - rather than the method belonging to the class, the implementation of the method to run is chosen based on the types of the arguments. This is similar to how Java works with instance methods, except that Java only 'dispatches' based on the type of 'this'. Because the method is not tied to the object in Lisp, what we'd normally call 'this' in Java may be the third or fourth parameter in Lisp - and dispatch can occur on any parameter.

That's one reason why Lisp doesn't need the visitor pattern. The next reason is that any good Lisp programmer would encapsulate the visitor pattern, probably in a macro, and reuse it. It's no longer a pattern, but a utility. The repeated code is hidden, unless there's a problem with the macro.

What had you do in the Lisp example when you want to introduce another Strategy instance?



Add another (defmethod) with the new type. This can be done at any time, including runtime. Looking again at that blog, I can see that it would benefit from being based around a real example, rather than choosing algebraic names.
[ March 09, 2007: Message edited by: Ricky Clarkson ]
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ricky Clarkson:
Imagine that you could, with Java, at any time, add a method to a class.



That would be a nice feature, but it wouldn't replace the Strategy pattern.

Using the Strategy pattern, I can configure single instances of a class, or even single method calls, so that they behave differently.


Common Lisp does it in a reverse way - rather than the method belonging to the class, the implementation of the method to run is chosen based on the types of the arguments. This is similar to how Java works with instance methods, except that Java only 'dispatches' based on the type of 'this'. Because the method is not tied to the object in Lisp, what we'd normally call 'this' in Java may be the third or fourth parameter in Lisp - and dispatch can occur on any parameter.



Yes, Multimethods. Again, don't replace the Strategy pattern.

If I could temporarily add a method to an *instance* (doesn't Ruby allow something like that?), it could work for single threaded code. I don't see any advantage over the Strategy pattern, though.

That's one reason why Lisp doesn't need the visitor pattern.



Correct. I've never worked in a language with multimethods, but I would like to. Perhaps I will try Nice some time.

The next reason is that any good Lisp programmer would encapsulate the visitor pattern, probably in a macro, and reuse it. It's no longer a pattern, but a utility. The repeated code is hidden, unless there's a problem with the macro.



You seem to imply that a design pattern is always repeated verbatim. That's simply not the case. Every design pattern is a template that needs to be adapted to the situation at hand (the Visitor pattern probably needing not as much adaption as others). There a dozens of Java tools out there that strive to make the use of design patterns "easier" - from simple code templates over code generation to class libraries. Because of the above reason, none of them work very well.

Which doesn't mean that I wouldn't write macros in Lisp. I just doubt that I had a single manifestation of every design pattern.

Add another (defmethod) with the new type. This can be done at any time, including runtime. Looking again at that blog, I can see that it would benefit from being based around a real example, rather than choosing algebraic names.



The longer I look at the code, it seems to me that it just is a Lisp version of the Strategy pattern, not a substitute for the pattern. It looks similar in intent to me to a C function pointer.
 
Ricky Clarkson
Ranch Hand
Posts: 131
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

You seem to imply that a design pattern is always repeated verbatim. That's simply not the case. Every design pattern is a template that needs to be adapted to the situation at hand



Most of the time, the adaptation is simply in changing the names used to match the current domain.

You might find this presentation by Peter Norvig useful, if you have an open mind: http://norvig.com/design-patterns/

He seems to address Strategy better than the blog I pointed at, and he's not using syntax, which probably helps.

The longer I look at the code, it seems to me that it just is a Lisp version of the Strategy pattern, not a substitute for the pattern. It looks similar in intent to me to a C function pointer.



In other words, what is trivial in Lisp needs a name in Java/OO because of the amount of extra code that it needs. I could take this common pattern:



I could call that the Pretending InvokeAndWait Can Return Stuff pattern, and I could repeat it happily, maybe even writing code generators for it, and applying it to other methods besides invokeAndWait. However, I'm not happy! I can see the same code in multiple places, and it makes me doubt whether I've finished thinking on this.

One solution that works for this particular case is to write my own version of invokeAndWait, that can return things. Generics makes this not so shabby:



I didn't use an array because new T[] would be a compile error.

Now, that works fairly well, but I end up with repeated code, because I have to do that for other methods than invokeAndWait. The next level would let me make any void method that takes a Runnable appear to return stuff. To refer to a method, I need to wrap it in an instance, so I'll need an interface:



Then here's the method that does the job:



Now I have factored out the duplicated code as far as is possible in Java. The awkward point now is that because I have to write a wrapper around a void method to make it into a VoidMethod, there's still some boilerplate, but it's more trivial boilerplate, that will hopefully disappear if method references get into Java 7.

So now you can see that I've taken a pattern, finished abstracting, and now all I have is a fairly trivial method.
 
Ilja Preuss
author
Posts: 14112
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ricky Clarkson:
Most of the time, the adaptation is simply in changing the names used to match the current domain.



If you are doing that, I can see where you are coming from. It's not how patterns are intended to be used, and it seems quite limiting to me.

"Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice." - Christopher Alexander


You might find this presentation by Peter Norvig useful, if you have an open mind: http://norvig.com/design-patterns/



Looks like a good presentation, although I didn't see anything mindbreakingly new while skimming over the first dozen of slides. Did you have anything specific in mind?

He seems to address Strategy better than the blog I pointed at, and he's not using syntax, which probably helps.



Seems to me like first-class functions can't very well replace Strategies with more than one method. In fact, I wouldn't say at all that they replace the Strategy pattern - they simply can be used as an alternative implementation style for the Strategy pattern when available.


In other words, what is trivial in Lisp needs a name in Java/OO because of the amount of extra code that it needs.



In Smalltalk, it doesn't need more code than in Lisp, and it still has the same name.



So now you can see that I've taken a pattern, finished abstracting, and now all I have is a fairly trivial method.




Are you trying to do a Proof By Example here?

As an aside, your "trivial method" is using the Strategy pattern, twice.

As another aside, I have a dislike for the "holder collection pattern". I'd rather use a local runnable class that remembers the value in a field.
[ March 11, 2007: Message edited by: Ilja Preuss ]
 
Don't get me started about those stupid light bulbs.
reply
    Bookmark Topic Watch Topic
  • New Topic