Jump to content

Talk:Advanced Java

Page contents not supported in other languages.
Add topic
From Wikiversity
Latest comment: 17 years ago by Cremisis13 in topic Events and GUIs

Elements of Good Design

[edit source]

I'm not sure how advanced this section on advanced Java is supposed to be, but I feel strongly that it should begin with an introduction to OO fundamentals that, while fundamental, are not basic. To ensure it belongs in a Java course, as opposed to a language-agnostic course on object-oriented programming/design/analysis, it should be heavily Java-centric. Also, I feel strongly that it be introduced at the beginning of any discussion of advanced Java topics, as numerous examples of the principles disclosed herein appear throughout the subsequent topics (Collections API, anyone?).

Here is a very brief summary of what I would like to see discussed in the subsections I've included...

The compulsion of all new OO programmers is to reuse code with little regard for the proper mechanism of reuse. To set up this course on more advanced topics, I believe it would be invaluable to point up the differences between the different kinds of reuse and when it is appropriate to employ each.

For example, inheritance is not appropriate for semantic reuse, but rather only for behavioral reuse. Though the Shape class and the Cowboy class both define draw() methods, that does not mean the behaviors associated with "drawing a shape" and "draw, ya varmint!" are shared—hence it would be inappropriate for these two classes to both implement the same Drawable interface. Drawable.draw() could not possibly express a contract of behavior that is common to both shapes and cowboys.

A more subtle and appropriate example: is Square a specific type of Rectangle? Mathematically, there is no doubt, and many developers would therefore allow Square to extend Rectangle. In most applications (not all, however; context is important) the mathematical truth does not equate to an OO truth. Because the intrinsic characteristic of Square has only one degree of freedom (I'm talking about dimension here, where the degree of freedom defining that characteristic is side length), Rectangle's dimension characteristic has two degrees of freedom (length and height). Hence, there is no good way to implement Square that extends Rectangle in such a way that a Square could always polymorphically—behaviorally—be treated as a rectangle. One can easily imagine a method that takes a rectangle, sets the width, sets the height, and expects the area to then be equal to the product of the two. Pass this method a square, however, and rectangle-type behavior will not be observed, causing the method to make faulty assumptions about area being the product of the prescribed height and width. Or, imagine a method that attempts to change a rectangle's dimensions to a prescribed aspect ratio while maintaining its area. Good luck getting a square to behave like that!

A second important issue of reuse that should be taught as part of an advanced course is that of aggregation vs. association. (There are many different types of "contains" relationships, but I feel these two are of particular importance and deserve special attention.) The primary difference being: two objects that share an aggregation-type relationship have similar lifetimes because one exists as a "part of" the other. For example, a building "aggregates" a room—without the building that contains it, a room cannot exist. Association is a weaker type of "contains" relation, occurring when two objects become associated with each other for a time but have independent lifetimes. A surgeon "associates" with a scalpel for the duration of a surgery, but there is no whole/part relation here. Before or after a particular runtime scope (an operation), the surgeon need not have access to a scalpel nor a scalpel be used by a surgeon for existence of either to make sense. In contrast, what does it mean to have a building with no rooms, or a room with no building? Interesting philosophical questions, but they should not be explored in the context of an OO system. :-)

The point here is to express that behaviors of objects are expressed by contracts associated with methods, independent of code. Indeed, and interface is useful precisely because it exclusively expresses this contract and prohibits code. Until this clicks misuse of reuse abounds.

(Reference: Liskov Substitution Principle)

Encapsulation

[edit source]

Everyone learns about "information hiding" as part of the basic OO introduction, but very few people really see the point of all those accessors and mutators (getters and setters) that simply get and set fields in one line and do nothing else. Indeed, many of those fields are often declared protected instead of private, and manipulated directly by subclasses.

public class Wrong {
    // should be private!
    protected int x;

    public Wrong() { this(1); }

    // should use own setter!
    public Wrong(int anX) { x = anX; }

    public int getX() { return x; }
    public void setX(int anX) { x = anX; }

    // ...
}

I want to extend Wrong such that whenever x's value is set, an event is fired off to notify listeners throughout the system...including when its value is initially set. Were this class designed properly, I should be able to override setX() and call it a day. However, since this class does not encapsulate its data properly, I cannot be confident in my implementation.

For starters, I'm forced to override both setX() and the constructor Wrong(int). If I don't have access to the source code, I can't know that the default no-arg constructor calls Wrong(int) so I have to override that too; in fact, even if I do have access to the source, if I don't override the default no-arg constructor as well my class becomes fragile. If someone were to change the no-arg constructor to simply set x directly instead of forwarding the call to the other constructor, it would seem this could not possibly break anything, yet that's exactly what would happen. Instances of my class that are created by chaining through Wrong's default no-arg constructor would now no longer behave properly, and listeners would miss out on the initial value of x the instance of my subclass is created.

It seems that overriding both constructors and setX() would solve the problem...but, we have to look at the "..." in the code above. If any other method manipulate x directly without going through the setter, my subclass has to override those as well. So much for code reuse through inheritance—it seems my subclass may have to be intimately familiar with the code-level details of how Wrong works, and then override those methods. Furthermore, as features are added to this application we must hope that future changes to other methods leave x alone (or use the setter), lest we have to go back and override those in our subclass.

And we're still not out of the woods! If any developer extends our class, they have direct access to x as well because it was declared protected instead of private! On this count, we're sunk...there's nothing we can do to help our subclasses do the right thing.

It's hard to believe such a simple class can be fraught with so many problems. Much better if it was done right in the first place:

public class Right {
    private int x;

    public Right() { this(1); }
    public Right(int anX) { setX(anX); }

    public int getX() { return x; }
    public void setX(int anX) { x = anX; }
}

Now, extending Right is a snap:

public class SubRight {
    public void setX(int anX) {
        super(anX);
        // notify listeners
    }
}

(Reference: Open-Closed Principle)

Dependency

[edit source]

Most beginners and intermediate developers take a short view of what it means to depend upon another class. If Foo needs to call a method on Bar, it's a simple matter—just obtain access to a Bar instance somehow and call the necessary method.

In reality, it's not quite as simple as that. It is necessary to understand that, first, Bar is an agglomeration of behaviors defined in Bar as well as every class an interface in Bar's inheritance hierarchy. It is not enough to simply understand the API of the method being called, the developer must ensure that it is being invoked properly within the context of Bar's entire API. That means understanding every method, the contract defined by Bar as a whole. And since Bar's contract accumulated from all of the classes and interfaces in its hierarchy, strictly speaking it is necessary to understand those contracts as well.

Bar may seem very simple at first glance, but if it extends a class like javax.servlet.HttpServlet, for example, it also inherits that class' contract that it must be run in a particular kind of container and support participation in a defined lifecycle and anything else true for HttpServlet.

A more immediate example that bites many new Java programmers occurs when they first override equals(), defined on java.lang.Object. If one does this without bothering to understand the contract defined on Object, their class will now break in ways both subtle and spectacular, for that contract stipulates that equals() must only be overridden in concert with hashCode().

And it doesn't end here...not only does Foo depend on Bar and the classes and interfaces defined in Bar's hierarchy, but also indirectly on the APIs that Bar uses. This does not seem like a very big deal until one decides that Foo should do all of its logging with the latest version of Apache Commons Logging, only to realize that some class employed by Bar deeply into the dependency graph is using some ancient version of the same package with an incompatible API.

(Reference: Dependency Inversion Principle)

Evolution

[edit source]

So what separates an "advanced" OO developer from a novice? If we review all of the issues addressed above, they all share one thing in common. That is: a novice sees an OO system as something that, one day, will reach a completed state. An advanced developer views an OO system like the corporation or entity that owns it, as a continuing and on-going interest that will never be complete. (It's true; the definition of "corporation" includes the idea of an on-going, indefinite interest.) Indeed, the only OO systems that are ever truly finished are those that have become so unwieldy and unmaintainable because of classes like Wrong that they are scrapped wholesale and reimplemented from the ground up—not by choice, but by necessity.

Advanced developers view code, therefore, as being in a perpetual state of evolution. The more concrete the idea of future direction and requirements for the system, the more intelligence can be built into implementation choices today. Wrong breaks almost immediately upon any attempt at extension. At least Right stands a chance of being around in a few years after requirements have come down the pipeline asking that it (or subclasses of it) manage not just x, but y, z, a HashMap of ks, etc.

(Reference: Stability, Interface Segregation Principle?)

Final Thoughts

[edit source]

I realize this turned out to be not quite as brief as I'd initially intended. What can I say, I get carried away sometimes. Anyway, I will be greatly appreciative if anyone adapts this content for inclusion in the pages themselves. (Or I'll do it myself when I have more time...)

Severoon 07:04, 3 July 2007 (UTC)Reply


Does Advanced Syntax Section Belong Here?

[edit source]

I can understand if we kick off Advanced Java with a review of the material covered in the basic and intermediate resources, but should the Advanced Syntax section be covered as a new topic in the advanced topic? It seems to me that all of the basic language features should be covered in beginning (operators, including the bitwise operators covered here) as well as literals and basic math. For instance, the NaN that every beginning coder deals with at some point should be addressed up front, along with integer math—meaning division truncation. Literal representation should be covered there as well, with attention paid to declaring float and long literals, bits, binary/octal/hex notation, etc. These are all very basic, memorization-type activities appropriate for an introductory course on the language.

The intermediate section should cover more advanced language features and Java's support of OO concepts. Language features might include generics (perhaps leaving wildcards for advanced?—maybe not, though, as they're used extensively in the Collections API so it might make sense to cover them earlier rather than later). Support of OO concepts for an intermediate level course might include how Java currently addresses closures (anonymous inner class w/final references), etc.

(Perhaps this isn't the right place to discuss this—if not, please move to the appropriate place and link here. Thanks!)

Severoon 21:27, 6 July 2007 (UTC)Reply


I think that all of the syntactic elements including those that support OO concepts should be taught as early on as possible. I'm not sure what material should be covered in intermediate or advanced Java but it definitely shouldn't be any part of the syntax. Dmclean 12:40, 9 July 2007 (UTC)Reply

Events and GUIs

[edit source]

Wouldn't it make much more sense to learn event-handling as you learn JButtons and other components in the Java Tutorial? I mean, what's the point of a JButton that can't do anything? — Preceding unsigned comment added by Cremisis13 (talkcontribs) 14:57, 8 December 2007 (UTC)Reply