Re^5: Re-blessing || Re-constructing objects
by dragonchild (Archbishop) on Apr 18, 2006 at 02:36 UTC
|
Have the user object contain an authorization object. Then, when the userobj wants to know if it's allowed to do certain things, it asks the authobj. The authobj replies with a boolean yes-or-no.
The reblessing you want to do is really changing the authobj. So, just change the authobj! There's no need to get all fancy. In fact, a good rule of thumb is "If I have to ask if something is possible on Perlmonks, I probably should rethink my design." About 90% of the time I ask a question, my design changes. This is a good thing.
My criteria for good software:
- Does it work?
- Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
| [reply] |
|
|
This sounds like compartmentalizing the aspects that might vary (method results) into a seperate object (authobj). Then, if you need to change how something works, you swap out the authobj for another one.
That sounds like more work to me, and for what? The method variations still have their own spaces, seperate by class lines, but you're suggesting using multiple objects where I only need one. If I needed authobjects for other types of objects I could see that being a compelling argument (design the piece, then plug in where needed). But it seems like a smaller change to start using a different name (rebless) for an object than to throw away part of it, create a new part, and plug it into the hole.
For clarification of my motivation, I'm not asking if this IS possible (I know it is, conceptually and in practice) but why ELSE it might be desirable (or undesirable.) I'm certainly not afraid to change my ways- if I was I wouldn't be re-implementing my current project with OO code after months of development. I'm also open to accepting I am misguided- but it takes a clear argument tthat puts me back on course to convince me.
"One is enough. If you are acquainted with the principle, what do you care for the myriad instances and applications?" - Henry David Thoreau, Walden
| [reply] |
|
|
The key concept you're not grasping is the fact that change is something that needs to be compartmentalized because it is the only safe way to build a large system. That system could be a piece of code or a bridge. You have to isolate change or that change will percolate throughout the entire system willy-nilly. In the case of a bridge, the bridge falls down. In the case of a piece of code, this means that every bugfix and/or enhancement might change the behavior of any part of the system, especially those which weren't touched.
What does this mean? Well, under your proposed system, it means that by fixing a bug in how User::Superman behaves, you might have changed the behavior of User::Anonymous. I'm pretty sure you would agree that this is undesirable.
The solution is to isolate the part that changes. Every user needs a set of authorizations, but that set (as a whole!) will change. So, isolate (or encapsulate) the bit that changes.
It is a tiny bit more upfront work. Yet, you will discover that you have more capabilities to the system than if you gone with your system. It's a bit of a leap of faith, but I hope you will trust me and be willing to make it, for your sake.
My criteria for good software:
- Does it work?
- Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
| [reply] |
|
|
|
|
|
|
It is more work. It also gives you abstraction between the classes. You get to use the public API, rather than having to know all the internals in multiple places. Think of the poor guy who comes along in a year and has to change something about the implementation of the AnonymousUser class, only to discover that the AdminUser class is now broken on some other part of the site. What a debugging nightmare.
| [reply] |
|
|
sub method1 {
...
if ($self->authenticated) {
...
} else {
...
}
...
}
sub method2 {
...
if ($self->authenticated) {
...
} else {
...
}
...
}
sub method3 {
...
if ($self->authenticated) {
...
} else {
...
}
...
}
instead of a nice set of
package User::Anonymous;
sub method1 {
...
}
sub method2 {
...
}
sub method3 {
...
}
package User::Authenticated;
sub method1 {
...
}
sub method2 {
...
}
sub method3 {
...
}
I think checking inside the methods is (security) error prone, and a lot more work, resulting in uglier code that is harder to maintain. Besides that, it's less efficient because of the extra check (which if you abstract properly, is a method call). I prefer doing something that isn't entirely pure in the sense of "how OO was officially meant" (if such a thing even exists) to something that is pure, but causes me and the future maintenance guy extra work. And OO purity is a performance killer in many ways, which is a problem if you happen to work for one of those shops that still care about that.
So you can't easily subclass anymore. Fine with me, because you can still wrap. If I properly designed my User, I'd let you add arbitrary states like Anonymous and Authenticated, and you could change things through that interface. Personally, I don't think inheritance is holy enough to have as a primary design objective. Composition is more useful IMO, and reblessing to change state (combined with inheriting (or mixing in) from a state independent class) is a primitive way of doing that.
| [reply] [d/l] [select] |
|
|
Yes. However, I tend to put my authentication code in my cgiapp_prerun and, very rarely, refer to it in other places. This reduces work quite dramatically.
I am advocating a composition solution in favor of an inheritance solution. Are we just in violent agreement here?
As for not subclassing as easily, it's the reblessing solution that's harder to subclass. Not only am I restricted to a given implemention, but I have to support every single key used by every version of those other implementations. What a nightmare!
My criteria for good software:
- Does it work?
- Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
| [reply] |
|
|
|
|
|
Re^5: Re-blessing || Re-constructing objects
by perrin (Chancellor) on Apr 18, 2006 at 02:54 UTC
|
But what if the the classes are intentionally written and maintained with this in mind?
Then I'd ask why you wrote this code as OO, since you aren't getting a major benefit of OO: abstraction.
There are many common approaches for doing the kind of thing you describe that don't require breaking encapsulation. They're so common that they have design pattern names, like "decorator" (wrap one object with another), or "strategy" (delegate the handling of certain methods to different objects determined at runtime). You could also just use the original object's public API to get the data to populate the new objects (a "factory" pattern). | [reply] |
|
|
Here's a detraction I've missed so far: using classes like this doesn't easily lend itself to more than one volitile layer at a time, and so using internal objects would have an advantage in that respect, as they can be swapped whenever, in any order. Class change seems in line with more fundamental shifts in the nature of the object. At times, perhaps farming out work to a seperate object that you work indirectly with is a little excessive when you can cut out the abstraction layer by designing the classes that fill that space to the same specs. If the classes all rely on the same parent class but represent the differences between objects with the same parent, then switching classes is exactly the sort of abstractive exploit OO is designed to allow. Perhaps my thinking is incorrect, and I welcome correction.
You need to know what type of object to plug into a given role- this doesn't require knowledge of the object's data structure, but does require setting aside a space for that object inside the larger whole and knowing how to interact with it. It needn't know how to interact with the rest of the structure outside it's own space. Any object that you plug in needs to follow the same rules- fit into the same space and allow the same manner of interaction.
You've got to design or find a class that you can assign to manage that space within your greater object, and it has to share methods of interaction with other classes that might fill the same role, and respond in the same way (or you need to put an abstracting layer between the two as glue). So far, this seems like it could be addressed with either an independant object or an additional class layer.
But the class can't safely make assumptions about what is outside it's own space unless you are able and desire to take any extra effort to make it so. Breaking encapsulation, and the pros and cons, is beaten to death elsewhere. But you needn't break encapsulation, if you restrict yourself to that space you know is assigned to you. You just need to allow, or even expect, that you might find data there you didn't create or put there- and maybe that's OK. In fact, maybe it's more than OK.
I recognize the benefit of reading up on patterns. Are these particular patterns all presented in the gang of four book, or can you suggest another?
| [reply] |
|
|
Then I'd ask why you wrote this code as OO, since you aren't getting a major benefit of OO: abstraction.
Dont you think thats a bit extreme? Simply because you have tight coupling doesnt mean you have sacrificed abstraction. If you can even speak of such a thing in such a way. For instance tight coupling between two classes doesnt mean that you lose any interface abstraction on the objects themselves. If A and B are tightly coupled C need not even know or care about one of them, and treat the other as simply and abstract interface.
I think it really depends on which attributes of OO you think are most important. If you think that inheritance support is the objective then I can see why tight coupling of two classes would bother you. You can't easily subclass[1]. But if you are more concerned with interface abstraction then it makes no difference. And personally ive found that interface abstraction (polymorphism) is much more commonly depended on than inheritance. If you are sane you only subclass things are designed to be subclassable in the first place. Especially in perl.
[1]: This is not strictly true. If both classes defined a sister_class property that returned the other classes name, then the tight coupling could be overriden. If you wanted to subclass either youd have to subclass both. Or you could pass the other classes name back and forth as parameters. This would mean the coupling was a lot weaker. IMO whether it made sense would depend on the circumstances.
---
$world=~s/war/peace/g
| [reply] |
|
|
How can you have any abstraction between two classes if an existing object of one can be blessed into the other? That isn't using the public API; it's knowing the full gory details of the implementation. Otherwise, how could you know it would work to bless from one to the other?
| [reply] |
|
|
|
|
|
|
|
Then I'd ask why you wrote this code as OO, since you aren't getting a major benefit of OO: abstraction.
Because I want to take advantage of another benefit of OO - polymorphism?
There are many common approaches for doing the kind of thing you describe that don't require breaking encapsulation.
Yup. But sometimes adding that extra layer of indirection makes things harder to understand rather than simpler, and sometimes breaking encapsulation of implementation doesn't hurt anybody.
I agree that reblessing is usually a sign of a design gone wrong - but sometimes it is the simplest solution. And that's fine with me as long as it doesn't get in the way elsewhere.
| [reply] |
|
|
The person trying to debug this code later may not agree that doing something so strange and unexpected just to save a few lines was worth it.
| [reply] |
|
|