@funky - great read - thanks! Also - good links - good stuff!
Printable View
@funky - great read - thanks! Also - good links - good stuff!
Yes you can avoid OO entirely and come up with something clean using only functions if you wanted. But its futile to pretend that VB6 can solve this with some jerry rigged wannabe OO mess better than a language with more powerful OO implementations like JavaScript, or C++. If I were doing this in VB6, I wouldn't even be thinking in OO terms to solve this rubber ducky business. I'd probably use bit fields and a single class with all of the implementation for all the types of ducks. The bit field would specify what the duck can and cannot do. I wouldn't go with all that over engineered mess with class factories and all that nonsense in VB6. That's just asking for a world of hurt.
VB6 is not a proper OO language.
Funky's won't open for me, but a few more lines does not make it less maintainable. We could do the opposite: have a bunch of ducks already in the system and properties set, and then modify which ones fly. Instead of modifying the ducks themselves, you're again, going into core code and there then comes the possibility of screwing up your if statement. God forbid you have the property specified twice in the large if statement you could have and you only changed one instance of it. Especially if you have more than an "if/else". I don't care if you want to put your fingers in your ears and go "No I'm not!," it is a simple fact you are. If you wrote a web form handling user input, would you have
if first_name then
else if last_name then
else if email then
else if address then
else
end if
? Of course not, that is absolutely ridiculous, yet with what you've said, that is a "behaviour class" because it is handling the behaviour of the form.
Wow, you're 100% focused on the amount of lines of code, eh? Maintainability != # of lines of code. If everyone thought like you, there would be no such this as dependency injection. I would much rather write an interface for every service I implement so I can fake it out for my unit tests and write proper integration tests, than have one class and have to override everything for testing. A million times so if DI is involved!
Having individual classes for each duck and having one place to modify these is a lot more intuitive than having to go into x number of classes to set the properties of the duck. If doing the opposite was more intuitive, why would properties on a class even exist? Everything could just be a method that calls a method from one of your behaviour classes.
<Cleaned up by mod>
PS: Sorry to any mod who may need to clean this up... I tried to tame it down as to not violate any conditions.
I don't think Niya's saying VB6 cannot solve the problem cleanly. Rather he's saying that it won't be clean if you try an solve it in an OO way. If you stick to a procedural approach VB6 would do a just fine.Quote:
I guess none of them can solve problems "cleanly", because according to you, "a full OO language will always solve the problem more cleanly than a partial OO language."
I have cleaned up some of it but I can fully appreciate your frustration. Olaf has been extremely condescending in this thread but, to be fair, so have I. And as a mod I really ought to know better.Quote:
Sorry to any mod who may need to clean this up
So here's the deal. Olaf, I won't condescend to you but you, in turn, need to stop condescending to others. If I see a problem with your approach I'll point it out but will do so as politely as possible. I expect you to extend the same courtesy to others.
And it's not just Olaf and I that this applies to, we're just the worst recent culprits. We should all be talking to each other civilly and trying to learn from each other. Learning requires admitting your own ignorance and that is near impossible to do if your pretty sure someone just going to try and humiliate you for it.
And may I apologise to everyone if I've shown a lack of grace.
That would make a lot more sense! ;) Niya, my apologies if I misunderstood.
I was just hoping to make the point that OOP is not the only way to approach all programming problems. Imperative programming has been used to solve a lot of insanely complex programs (e.g. the Linux kernel), while other paradigms like functional programming nicely tackle a whole other set of problems.
Multi-paradigm languages all have trade-offs. (If they didn't, we'd have "one master language to rule them all".) At the risk of incurring Olaf's wrath, I too find Funky's solution more elegant, but that could just be because I find OOP very intuitive for the "problem" he originally described.
I think one of the interesting things about this thread (which has gotten markedly better over the past 30 posts - thanks to Funky in particular for kickstarting a great discussion!) is that the "cleanest" approach to a problem is going to vary based on your familiarity with a given tool/paradigm.
This happens to me all the time with functional programming. I look at something like Haskell code, which can do so much with so little, but wow, it is really hard for me to wrap my head around that problem-solving approach. 10x more lines of an OOP or imperative solution is easier for me, and even though it's less terse, I agree with kfcSmitty's great point that Maintainability != # LoC.
I'm pleased as punch with the direction we're taking now, real knock-down drag-out fights over how to implement a problem! It's a shame I don't have a copy of VB6 to read most of the code, but I can kind of follow along because this particular problem's familiar. But first, some comments.
I learned squat about writing *good* code in college. College taught me how to write functional code. It was only after I left and started mingling with other professionals that I started figuring out there were reliable practices that led to better code overall. I found more professionals with that focus outside of the VB community than inside. Inside of the VB community I encountered more people who resisted those ideas. IMO, everyone is self-taught because I've yet to see a college with something I'd call a true software engineering program. 4 years is barely enough to scratch the surface.Quote:
Originally Posted by szlamany
This is something I can work with way better than the earlier sarcasm.Quote:
Originally Posted by JackB
VB6ers don't comment on where the .NET devs should go (well, they do, but the destination's not polite...) because if you're a VB6 dev and VB6 is the only language you use, you're going to suggest VB6. And we know where that ends up.
In 2008 or so, if you knew C# and did WinForms, you got a lot of pressure to move to WPF. If you moved to WPF, you got a lot of pressure to give Silverlight a try. Then WinPhone. And so on. I saw a ton of people jump ship to Ruby/Obj-C, and code meetups that used to have a lot of discussion about Windows turned into me sitting by myself while everyone else talked about iOS and Rails. It stunk. I bet this is what being a VB6 dev felt like, and still feels like.
But as I survey the world right now, I see C# everywhere. It's on Mac, Linux, iOS, Android, Windows... If you know a little WinForms and a little XAML, there's no UI platform that's alien to you. I don't write Windows clients anymore, I write iOS/Android apps with C#. It was an easy transition, because all the skills built on one another.
VB6 is much harder to jump fences from. It runs... on Windows. The easiest move is "writing WinForms clients", but why invest so much effort to do the same thing it already does? The only benefit is you get more familiar with .NET concepts, but ultimately the route is to learn C#, touch some XAML, then find what looks exciting to you. That implies that you want to shift your career at some point. I'm starting to realize a lot of people don't. Personally, I think VB6 is a bad bet over the next 10 years, but I'm pretty sure the current crop of VB6 devs is well aware of that and quite happy with the risks. There's more productive uses of my time than hassling them, and I wish more .NET developers held that opinion.
I'm on board, if I find time to devote to it I'd love to have more discussions like this, very much so. It's a heck of a lot more productive than writing posts like this one. Lately I find I enjoy writing that kind of thing in C# more than VB and that makes me a bit of a leper to some people ;)Quote:
Originally Posted by FunkyDexter
Maybe I'll start with a series on SOLID, it segues real nice into the stuff Head-First Design Patterns did.
(continuing ducks problem)
I think it's getting kind of muddled. This example in terms of Head-First Design Patterns was a zoo, which included penguins, flying fish, and peacocks to have every possible combination of bird/not-bird, swimming/sinking, and flying/earthbound in addition to "every creature makes a different noise". Being limited to just ducks makes the prospects of using more complex patterns less desirable. It also feels like maybe each person's adding/modifying requirements, this kind of discussion's best if it starts with a formal problem definition and you lose points for solving problems that aren't present in the definition. "YAGNI" is a valid pattern ;)
Discussion advice via storytime: One time, long ago, I made the mistake of leaving a bad review on a book I had to read in high school that I felt failed at most of the things it tried to do. I attracted the ire of a ton of bibliophiles, but one stood out. After several ad hominems, he closed with a statement like, "I want you to write a five thousand word essay about post-modernism, and how <author> was influenced by <her childhood or something> and where this book fits in the context of <some other literary movement.>" He probably felt really smart, as if he were backing me into a corner in which I'd have to admit I had no literary analysis skills. Instead, I replied, "I'm not going to do your homework for you, I've got my own essay to write." I think I won, I owed him nothing. Had I wrote the essay, he'd have either ignored it or picked it apart for any tiny subjective opinion I injected. It wasn't worth my time.
No one ever owes you a demonstration. The argument form "Please <implement a complex project> showing this." is not an argument, it's a request for free work. I wouldn't answer a newbie question that made zero efforts, and I certainly won't spend time dealing with a complex OO topic if I think my efforts will be met with a hearty criticism. Here's an idea: you implement the technique you're sure is wrong, then criticize your own work and invite the other person to show you your flaws. Since trying this technique, I've found plenty of scenarios where halfway through my rebuttal I realize I actually started agreeing with the person's suggestion, or at least I could see some benefits. Get out of the habit of thinking there's a "right" answer to design topics. See them as difficult comparisons between several solutions, all with pros and cons. We don't get points for proving other people are wrong, we get points for helping each other learn new things.
In conclusion, here's a library I propose every developer should read through/strive to possess. You're going to have to learn some C# to read most of these, and occasionally Java. It won't kill you, and it's very rare to find a book that uses VB for these topics. It will make you stronger to apply these techniques to your VB.
- The Art of Unit Testing (Roy Osherove) - If you aren't writing automated tests for your code, you may as well not be bothering with any of the other resources. Most of the reason you want to modularize your code and use patterns is to facilitate tests. This is the only entry-level testing book I know, and it's likely the only one you need.
- The SOLID principles are the basis of testable design. It takes a long time for them to sink in. Just read them, and come back to them every now and then, re-reading the articles as you learn more tricks. When I first read them, I only agreed with a few points. Now, I ask why they were never obvious before.
- Working Effectively with Legacy Code (Michael Feathers) - "Legacy Code" in this case is "anything without unit tests" or, more appropriately, "any code without a provable definition of its behavior". This book is about safe techniques for isolating legacy code and slowly refactoring it so that it can be tested. As such, it's the only resource that shows you "bad" functional code and how it is expressed as "good" testable code. I bet 90% of you only have "legacy" code to work with, so it's a gem for you.
- Head-First Design Patterns (Freeman & Bates) - After you read TAoUT, this is a great way to learn more tricks for isolating chunks of code from each other to facilitate testing. It's far more approachable than the denser pattern texts, but definitely nowhere near as complete.
- Clean Code (Bob Martin) - This is short, sweet, and will feel very familiar after the previous two. Uncle Bob narrowed in on the fundamentals of testable design before anyone realized what it was.
- Code Complete [really long title] (McConnell) - This book is language-agnostic enough to contain some examples in VB. It is a nuts-and-bolts guide to trying to write more readable code, less focused on project architecture than it is on statements at the level of while loops. It's still incredibly valuable, and discusses readability tricks that'll make you smile.
Add to that list anything by the authors in that list. Fowler & Martin are particularly well-known and have written many books. They're also associated with agile circles, which may or may not turn your stomach, but we can cut big bits of "agile" out of that methodology and still be left with a great process for writing maintainable code that proves it operates correctly. You don't have to be getting a paycheck for that to make your life happier.
Where's the reference texts like Gang of Four? They don't make good reads. It's a lot faster to describe your problem to others and ask how they'd solve it, then look up any patterns they mention. I want them on my shelf, but most of the information's somewhere online as well.
Also I'm still kind of mad to see people bickering about whether having OO features makes the language good or not. It's not a discussion. If you think a language is generally better or worse based solely on having OO features, you are wrong. We can narrow the context to, "Well by not having THIS implementation, it can't solve some problems well..." but it is very rare to find a language that is just all-around bad for all solutions outside of esoteric languages like lolcats. If you want to have that discussion, I'm going to be the guy who tells you nothing is ever as elegant as LISP.
And the last thing we want is to attract the attention of a LISP evangelist.
Yep...that's exactly what I'm saying. Doing otherwise is like I said, trying to fit a square peg in a round hole.
https://encrypted-tbn0.gstatic.com/i...NcQfYVK0x0UrIA
You paraphrased me a bit - I said this
and it was in response to Schmidt going off on self-taught and/or self-employed people.
I've been coding since the 1970's (both employed, self-employed and partnered with others) - I have no idea what is taught in colleges today. I would hope it's C-syntax and Java and Ruby and all the other languages I get emails from head hunters about.
If I was going to hire a college coder today I would be more interested in what they have done with maybe C++ or some other way to see the real problem solver / logical thinker they are.
If I had any time I would have worked up some JavaScript duckie classes :duck:
As for poor spelling - I'm always interested to better my english - and wouldn't mind
(although pointing out spelling-mistakes is considered quite rude under the rules of
general netiquette) when you'd just point out the mistakes I made in my sentence...
As for "unsubstantiated insults" - what if I'm right?
If you think I'm wrong, then I'll ask you basically the same as I've asked kfcSmitty already:
Please extend your original-example:
- about the DuckTypes 'Redhead', 'Canvasback', 'Hardhead' and 'LeadenDuck' (the latter one sinking instead of swimming)
- change your Display-handling to be able to read the Description of the DuckType in question from a Resource-table
Then post the resulting differences in code.
I will post my VB6-code-differences after applying the same enhancements to my implementation.
Then let's compare notes, which of the two versions is "a mess" and which one was
easier to maintain.
To the VB6er and .NETers who might be wondering why I'm asking for this so adamantly...
The outcome will be in favour of the VB6-version (by miles and bounds) - that's why
any such request was (so far) ignored.
My guess is, that we will see more of the same - which is kind of sad -
since "in code is truth".
That's an assumption (and an insult by the way) - and I see that you claimed in further
postings of yours, that my behaviour is "condescending" towards others.
I'll ask you this (again) - what if I'm correct? What if the diffs you will
produce clearly show, that I was right all along? Is telling the truth in
a forum for technicians (where NewComers could be easily misled, when stuff
remains uncorrected and unwithspoken) now considerd "condescending"?
Consider this Funky. Patterns are there to be applied to a certain scenario, which
when applied *correctly*, will offer advantages over the alternatives, right?
If only *one single example exists*, which implements the same scenario better,
then either the pattern was a useless one - or it was wrongly applied.
For the sake of the OO-Purists I will state, that the strategy-pattern is
*not* a useless one - but now, who was it exactly, who applied it wrongly?
Not me (I've applied a different, more efficient pattern to the scenario).
So, how would *you* characterize somebody who couldn't come up with an ideally matching
example-scenario, to demonstrate the advantages of a given pattern *very* convincingly?
Could it be, that this person didn't really understand the *pattern* (and where
to apply with an ideal outcome) in the first place?
Absolutely, because I actively *avoid* "heavily complex Class-hierarchies"
(they are considered "bad OO", you know).
A Select Case has its place in programming (otherwise switch-constructs would not exist).
To apply the proper mix is the key - besides - if my Case-Switching would have become
a truly unmanagable one (which it so far definitely isn't) - my design allows also for
behaviour-Classes which are dedicated to a single case - but I've explained that already.
Not sure how you do *your* tests, but I would test *all* of my Ducks for the correct behaviour.
I could do that for each behaviour separately - or for all behaviours at once (the Form-Code I've
provided in my Demo, did that already - listing all behaviours "visually" for all current ducks) -
but if you want, I can show you dedicated TDD-sequences, implemented in a cTestBehaviours-Class -
there's TDD-HelperTools for VB6 which ease the formulation of these a great deal).
Coding to an implementation in VB6 is the same as coding against an interface.
I've explained the mechanism already whilst showing dedicated single behaviours
(without any Select Case) - in the DuckDance-scenario a few posts above.
Nope, they are not.
In VB6 I can produce a TypeLib (in case the Duck-Classes and Duck-behaviours were sitting in
a COM-Dll-Project) with a single MouseClick on one of the CompileDialog-Options of that Dll.
And then - in case I'm interested in outsourcing the implementation of the "standard-swim-
behaviour" of my ducks, (currently implemented in the default-swim-behaviour-Class bDuckSwim)
to a chinese facility, where the discovery of a "quite large QuickSilver-Puddle" was reported -
I could send that TypeLib to somebody "on-site" - along with the request, to check this Typelib
into a new VB6-Dll-Project - and implement the interface of Class bDuckSwim in a new
bDuckSwimOnQuickSilver-Class of that "Plugin-Dll" - then testing (isolated) the behaviour
of all Ducks (espacially the leaden one) against this new implementation in the
bDuckSwimOnQuickSilver-Class.
And back the Dll is sent to me - and I can use it from within my Factory-Class
without the need to break any existing interface of my DuckBase.dll.
You are wrong.
Please give an example for such an extension as a clearly formulated change-request.
After that let the both of us implement that request and compare notes - it really is that simple.
No, I will not have to do that - formulate what you mean as a concrete change-request
to *both* our base-implementations - and then we compare notes.
No, any *professional* developer would disagree with you.
It would be your design which would be dismissed outright by a truly experienced authority
(who really knows what VB6 allows and what not).
Just post the code-differences for the change-requests, I've nicely asked you to apply
at the top of this posting - and then let's compare that.
Sure, I know all about that - but what you still don't really grasp is, that
any "non-virtual-base-class" in VB6 can serve as the interface-provider to
future implementations of polymorphic derivates which implement that thing.
I was following KISS in my first throw - and even *whilst* doing so, my example
is the more flexible one for the scenario by miles and bounds - it is in no
way "tightly coupled" as you claim - since it allows plugin-like and outsourced
behaviour-development from the start (in two different ways - standalone
behaviour-classes or Select Case based ones which allow easy "grouping").
Your turn again - and please provide the Diffs I asked you for, so that everyone
can draw his own conclusions from the results.
Olaf
Well, that's not a one-sided thing - it's "requests for free work" for all developers who'd participate
(for the sake of the community, which would surely benefit from the different code-bases and approaches -
when all try to implement the very same thing).
It's a well-known approach nobody who really cares about "the increase of knowledge"
in a given community would advice against.
One of the most wellknown sites for that kind of thing is:
http://rosettacode.org/wiki/Rosetta_Code
Here a comparison of different js-Frameworks, all offering implementations for the same project-task.
http://todomvc.com/
How is it helping other people to learn new things, when claims are made which don't
hold up to reality - and one leaves them unwithspoken?
As an example might serve: "Creationism".
When this stuff is taught at schools (I mean, the kids will surely get "great help to learn wondrous
new things") - how can this be considered a thing to "give points for", when those kids later
realize that most of these new things they learned, were a waste of time and for the most part
just plain wrong?
Olaf
How is that different from "screwing up the things you write into the core-code of the specific-duck-class")
But I'm repeating myself here.
You didn't even read or understand my example, when you write about "the property".
As it is implemented in my behaviour-classes currently, each of the 4 behaviour-classes
is directly representing, what you call "a property" (which is not really a property,
but a function BTW, which is a very different thing in the given scenario). Each of the
current Methods of the cDuck-Class maps to a single behaviour-class.
And what finally comes in over a parameter into these behaviour-classes, is again -
*not a property* - it's the DuckType itself (think: DuckID As Int if you want,
meaning: my example could be easily enhanced without changing the outside interface,
to work in conjunction with a "Ducks-Table" in a DB, where thousands of different Ducks
might be hosted, each having a unique ID - an ID which I could easily introduce in favour
of to the Enum-Value, which I preferred to work with to keep the example simple in my first throw).
Funkys example does *not* offer this possibility - and you cannot seriously tell me, that
you would implement thousands of specific duck-classes, when my next change-request
asks, to extend the example to this amount of ducks.
But also *that* was already mentioned in a previous post - though is still not recognized.
Again, pure ignorance, not even seriously looking at what I did in my implementation.
I repeat: I was *not* checking any Properties in my behaviour-classes (as you try to suggest
here with your little if-construct).
Give an example for a concrete dependency-injection for Funkys example - and I will show you that
I can do so as well (if not easier) in my example. It's really tiring - the solution to all that endless
bickering could be so damn easy, when we would concentrate on the "give-proof-by-code"-
pattern for a change. ;)
Olaf
Well, not sure why my responses always seem to be recognized as "coming-out-of-the-wrath-corner" -
I'm just passionate in defending my position - with good arguments I think - and with reasonable
and logic requests - or simple questions, when points of disagreement were reached.
So, what's your opinion on demanding difference-code from Funky and me as well of course,
to some change-requests we would both have to apply on our initial examples?
I'm quite sure, that only a few have understood my example fully so far - and those
diffs would simply prove how flexible it really is.
And to write it up again:
When sombody explains the advantages of a certain pattern - especially when mentioning this kind
of pattern as "impossible to do in VB6" - is it not reasonable for me to assume, that the example which
is then used to demonstrate this allegedly superior pattern, was a well choosen one - demonstrating
the best of the pattern?
But when this pattern "gets a beating" by a simple combination of Composition and Factory -
why does Funky not say: "Sorry my example was not well thought out to demonstrate Strategy -
here's a better example - now beat that!"
Maybe he will do so still - but his line of reasoning was - please remember:
You will not be able to ever achieve the maintainability "true OOP" has to offer.
If that's true - why was he not able to offer an example which *decisively* proves that?
As for Maintainability != LOC ... that's true - but LOC is at least a good indicator (for starters)...
another indicator is, how extensible something was designed, without the need to break
interfaces - another one being - how many different classes were affected to cover a single
change-request.
Olaf
It's OK to say "prove it", but more fun to try proving it yourself. Try it sometime. I find it much more satisfying to implement the "problem" and talk it out. Half the time I realize it's either not as bad as I thought, or that I was wrong. But folding your arms and putting all the burden of proof on the other person is a good way to make them not want to reply. The "winner" of this kind of argument is the one that stops posting first.
I've lost track of the original projects, or I'd dig them up and try to comment on them myself. It's dangerous work, because I don't have a lot of technical knowledge surrounding VB6 and how people implemented extensibility in it.
But the ducks example, like the zoo example on which it is based, is a trap for newbie OO developers. It is a situation that quickly escalates into one that creates the kind of mess that inheritance solves poorly. But to believe that, you have to be of the school of thought that this kind of method implementation is a sin:
I subscribe to this school. I'm shedding the VB syntax so I can be freeer in this non-code editor. If we have a base class Duck:Code:Public Overrides Sub Fly()
Throw New NotSupportedException("This kind of duck doesn't fly, it's a decoy.")
End Sub
Then all ducks must do something reasonable when I ask them to quack or fly. This has to hold because I want to be able to write code like this:Code:class Duck
virtual void Quack()
virtual void Fly()
But if I decide some ducks can't fly, then I have to change my implementations to be ready for NotSupportedException:Code:foreach duck in ducks:
duck.Fly()
I don't like this. Exceptions are for exceptional cases, and there's nothing exceptional about some ducks not flying. That implies that Duck, as a base class, should not have a Fly() method. If we take that logic further, it turns out the same argument says we shouldn't have Quack(), either. So what's the point of a Duck base class with no methods? Good question. Maybe we don't need one. So how do we get a list of all ducks AND call their Quack() or Fly() methods if we don't have a common base class? You ought to be able to think of quite a few ways.Code:foreach duck in ducks:
try:
duck.Fly()
catch NotSupportedException ex
' do nothing
For this problem I favor extensibility by composition and interfaces. I can make an Animal base class with the scant few behaviors that are common for all animals, which depending on the way the example goes might just be properties for name, size, weight, etc. Now, I do want to differentiate by species and maybe other things, so we can have a Species enumeration. If we expect to have thousands of species, that's very impractical for extensibility, so in that case I'd just use string names set up in a configuration file. We'll use an enumeration, because we've got Ducks and Kangaroos in our zoo and a 2-item enum is quite manageable.
Right. So you're rolling your eyes and wondering how we make an Animal fly. Well. It starts with an interface:Code:enum Species:
Kangaroo,
Duck
class Animal:
int Weight;
string Name;
Species Species;
Now, we could make a Duck base class that implements ICanFly, but then we've just added a layer of indirection to the inheritance solution. We can't have a Duck base class. But we can make some derived Animals:Code:interface ICanFly
void Fly();
Now I fully support Liskov Substitution Principle: every Animal has the same behavior. That makes it inherently safe for me to make something like this method definition to load all my animals from the complex zoo simulator database, or a test method that just spits out a few:Code:class GreenheadDuck
inherits Animal
implements ICanFly
void Fly():
Console.WriteLine("{0} is flying!", Name);
class DecoyDuck
inherits Animal ' Questionable, but required
So what if I want all ducks? Or what if I want to make just all ducks fly? I don't feel like going into all the details, but given some array of all animals, VB .NET's got facilities to let me write helper methods like the following without modifying the Animal or Duck classes:Code:Public Function LoadAnimals() As Animal()
"Fine", you say, "but what if you want a default duck flying behavior?" That's composition, friend.Code:ducks As Animal() = allAnimals.WithSpecies(Species.Duck)
flyingDucks As ICanFly = ducks.ThatCanFly()
flyingDucks.ForEach(duck => duck.Fly())
Now, if I want to add new behaviors, I don't have to go checking every base class. I do have to go checking every animal implementation. That can be a pain. VS has a cool code generator that'd let me write a quick parser that generates all that code from a text file, and if I had more than 5 or 6 species and 4 or 5 behaviors I'd definitely consider it. It'd have lines like this:Code:class DuckFlyingBehavior
void Fly(Animal duck):
Console.Writeline("{0} is flying!", duck.Name);
class GreenheadDuck
inherits Animal
implements ICanFly
new():
_flyBehavior = new DuckFlyingBehavior(Me)
void Fly():
_flyBehavior.Fly()
It could easily be smart enough to look for a class named "SparrowChirpBehavior" and wire it up in Sparrow's generated class. Fancy. I've done this once or twice. No examples because I've already run long and the syntax is something I can never quite commit to memory, but it's a fun form of dark arts.Code:CreateAnimal("Sparrow").WithSpecies("Bird").ThatCanFly().ThatCanChirpWithSpecialBehavior()
Now, OK, what if you don't think that method implementation is a sin? Then you get to use inheritance. You can sort of address this with the "Optional Feature Pattern":
It's a little bit messier, but you can still write code that does fun things like:Code:class Duck
inherits Animal
public virtual bool CanFly { get { return true; } }
public virtual void Fly() { Console.WriteLine(...) }
It doesn't handle cases like flying fish or flying squirrels well, though. You have to remember that there's some non-birds that fly. If you have ICanFly, that's a lot easier.Code:airShow = allAnimals.OfSpecies("Bird").ThatCanFly()
airShow.ForEach(bird => bird.Fly())
I don't think either's better, but some lead to more trouble down the road depending on just how big your zoo gets. You should do what's easiest and solves the problem now, then when it gets unmanageable do the next most complicated thing that solves the new problems elegantly.
You can call it overengineered, but I'd like to see a version of "all animals that can fly should fly" with inheritance alone that can respect squirrels and flying fish. Multiple inheritance can do it, but it comes with its own concerns and you can't convince me VB6 has multiple inheritance. Here's my implementation of "all animals that can fly should fly" because it's already implemented in my language of choice, here's a call:
First class functions are cute. It could be done on one line, but I like my foreach statements like I like my loud music: explicit. That loop will extract all ducks, birds (but not penguins or peacocks!), flying squirrels, and flying fish and make them fly. When I implement a new animal, I don't have to think about anything but what it can do, and I only have to implement the behaviors it needs. If it happens to share a behavior, I can extract that to classes and use composition. No one outside of that class will ever know, unless I choose to go the full-tilt Inversion of Control route, which is a testability consideration and a ridiculous boon for extensibility.Code:foreach (var flyingAnimal in allAnimals.OfType<ICanFly>()) {
flyingAnimal.Fly()
}
So yeah, that's how I think you ask for an implementation. Welcome to programming, where I'll tell you "never test in production" until the moment I reach a process so convoluted I can deploy a new version days faster than firing up a test instance. We have schedules to meet, and deliveries to make. Sometimes we bend our own rules and hope we have time to clean up the messes.
I'm sure you can come up with something, it's never impossible. I don't know enough VB6 to pull it off, and I think any inheritance-based solution will have to resort to dirty tricks if it wants to implement this feature. I sure as heck don't want to try a C# version with class inheritance. But this is just me being mean: no one's proposed that feature yet, we don't know if it's really needed. But it makes a heck of a case for interface-based inheritance instead of class-based inheritance as a tool for managing what would otherwise be extremely gnarly.
That is why the zoo problem is one you give green developers. To teach them why inheritance is but one tool on the belt. It is much like the "change" problem, which teaches them to fear floating-point math. You don't really have to defend your approach. This problem was crafted to be one that inheritance can't deal with. I'd use your approach on a small project with a well-defined set of critters that don't have problems like flying squirrels to deal with. And if I happen to know peacocks are the only birds in my zoo that don't fly, I might just live with a single special case. But when flying squirrels show up, I'll frown. And when penguins show up, that's when I will abandon inheritance. Use the tool that best solves your problem, and toss aside tools that don't. That's the /real/ lesson.
And if you even MENTION the ghost zoo, where no animals have weight, I'm going to get quite sour.
I agree with Sitten that inheritance solves this problem poorly....however, with a combination of inheritance, interfaces and polymorphism, you can do such wonders. I actually just realized that I faced this exact same problem relatively recently. The Demon Arena project(you can find a link to it in my signature) had me working with this rubber duckie problem. At first, I tried to solve it with only inheritance only to run into a wall as I started adding more stuff to it. For those of you who don't know, Demon Arena is a basic 2D engine where I put various creatures to fight against each other by mostly throwing projectiles.
Here's where OO actually made this such a pleasure to work on. You see there is a lot of stuff happening on the screen when its running. Creatures roaming about throwing stuff, explosions, move blurs etc. OO was able to model this environment very perfectly. Because no matter what, everything you see on the screen has an x and y and a current frame in the form of a Bitmap(or a cached DirectX texture if you want better performance). Right there you got your first and most basic interface, lets call it IEntity(its actually called this in the project). Every thing you see on the screen would implement this interface. I can then implement a simple base class for non moving special effects such as movement blurs and explosions. Different types of effects would inherit this base class and implement their own effect.
Ok, so what about creatures and projectiles ? Well this is why this approach was so beautiful. I created two separate base classes, one for projectiles and one for the creatures and both implement IEntity. The beauty about this is that when its time to render, I don't have to care what kind of entity I'm rendering or its implementation. Polymorphism allows me to treat each object to be drawn as IEntity objects and that interface provides only what I need for rendering, its position and its current frame.
It gets even more interesting than that. For example, certain creatures throw explosive projectiles and I wanted those projectiles to have an effect on creatures that are near the explosion, so I created an IThrustable interface and implemented it on the creature base class. Now, I want to point something out here, that is that this is can be a trap when doing this for the 1st time because your first instinct would be to implement this behavior on the creature base class itself. It makes sense. All creatures should be affected by explosions from their enemies. Here's where the trap lies. Remember the other base classes for special effects or you might event want different types of entities later down the line that you may want to be affected by explosions the same way. By creating IThrustable and implementing it on the creature class ensures that all creatures can respond to explosions and it also leaves me with the ability make other types of entities capable of responding to explosions.
I should also mention one of the greatest things I've discovered about using interfaces in scenarios like this. It reduces the amount of mistakes I can make when I have the discipline to code only against the interfaces. For instance, the code in the projectile class that deals with explosions don't have to care one lick about what its affecting other than the IThrustable interface is implemented and the compiler would enforce this. You can't mistakenly try to thrust something that isn't meant to be thrusted. All I need to be concerned with is calculating the correct values to pass through the interface. Each entity implementing the interface only has to worry about how its going to respond to the values passed through the interface.
The point I am making is that the trick to using OO is knowing when to use what. I did fall for the trap I mention above but fortunately I was able to correct it before I became too committed to using inheritance instead of interfaces. It took a while but I've developed an instinct for knowing when to use what.
BTW, I also did this same project in VB6 before and while I managed to make it work, it was no where near as clean or elegant, no matter how much I tried to make it so and I remember quite well trying a number of different patterns in the hopeless attempt to make the project manageable. I never found a clean way to model it in VB6 and it quickly became a huge mess and seeing how cleanly others implemented similar ideas in C++ using inheritance and function pointers, etc(I was looking at the Doom source at the time) only frustrated me even more that I couldn't do it like that. I finally gave up and stopped working on it. At the time I concluded that VB6 was never the language for this kind of thing. I was thinking that I should have used C++ instead. Of course that was long before I knew anything about what VB.Net or C# had to offer.
Now, this is not to say VB6 is bad, its just for these particular type of problems, VB6 was far less suitable as far as maintainability goes. I'm sure that with my current experience I can implement a far cleaner solution in VB6 but I'm also sure it would not be as clean as the VB.Net, a C# or even a Java solution. OO deals with this type of problem too well.
Well, the problem has changed a bit, didn't it?
Originally there was 4 different ducks which could Quack, Swim, Fly and, possibly, have a picture. A base class defined with defaults (Don't fly, Can Swim, Can Quack, No Picture), and changing "only" what doesn't fit for "not regular" ducks seems to solve the problem. What each property means is easily adaptable (call a function, instantiate an object, write a letter,... whatever). This is a simple way of inheritance: use a base class and change what needs to be changed for sub-classes. This is what Olaf did for the given problem. Simple and effective, even for a few more ducks with a few more properties.
Now, making this for every animal on earth would be insane. It would require a few days studying the problem, eventually defining Groups, Classes, Families, Species,... (or whatever zoologists use to classify animals).
But a Class named Animal would only make sense it there was a class named NotAnimal.
EDIT: I imagine that VB6 is able to call a function by name? if so a given property could be simply a string with a function name, or a class name...
This reminds of a fun (brief!) diversion.
Doom was actually written in C. So were Quake, Quake II, and Quake III Arena. iD didn't move to C++ until Doom 3, and even then, Carmack had concerns about his ability to use it well. This analysis of Doom 3's source code is one of my favorites - and I think relevant to this discussion - and if you scroll down you can see Carmack's response in the comments. Here's the bulk of it:
I like that opening quote a lot, "I still think the Quake 3 code is cleaner, as a final evolution of my C style, rather than the first iteration of my C++ style." A language you know and wield well is always going to result in clearer code than a language you don't know as well.Quote:
In some ways, I still think the Quake 3 code is cleaner, as a final evolution of my C style, rather than the first iteration of my C++ style, but it may be more of a factor of the smaller total line count, or the fact that I haven’t really looked at it in a decade. I do think “good C++” is better than “good C” from a readability standpoint, all other things being equal.
I sort of meandered into C++ with Doom 3 – I was an experienced C programmer with OOP background from NeXT’s Objective-C, so I just started writing C++ without any proper study of usage and idiom. In retrospect, I very much wish I had read Effective C++ and some other material. A couple of the other programmers had prior C++ experience, but they mostly followed the stylistic choices I set.
I mistrusted templates for many years, and still use them with restraint, but I eventually decided I liked strong typing more than I disliked weird code in headers. The debate on STL is still ongoing here at Id, and gets a little spirited. Back when Doom 3 was started, using STL was almost certainly not a good call, but reasonable arguments can be made for it today, even in games.
I am a full const nazi nowadays, and I chide any programmer that doesn’t const every variable and parameter that can be.
The major evolution that is still going on for me is towards a more functional programming style, which involves unlearning a lot of old habits, and backing away from some OOP directions.
It also makes me feel better that the greatest coder of all-time (yep, I went there) is also working at un-learning old OOP habits in favor of functional programming, where appropriate.
Like JackB before (whose sarcasm was quite funny, I thought), I think you need to read our comments with a little less literalism. I was just teasing you; I enjoy the passion on display. (However, I'm less fond of your use of hard line breaks, which cause your posts to flow very poorly on certain screen resolutions! ;) )Quote:
Originally Posted by Schmidt
Huh... I noticed the conversation has moved away from VB6-v-VB.NET to a more broader subject of Battle of the Design Patterns. Perhaps that's really the crux of the issue, not one of actual languages but the patterns and habits we use.
-tg
Agreed, I can't even find the original problem easily. A 20 page thread is no place for code golf. I altered the problem because it seemed like that's what was currently trendy: framing the problem in a way so that the "wrong" person's approach would have issues.
The thing I don't like about dueling patterns, as fun an opportunity to show off as it is, is it perpetuates the myth that there's a "right" answer. It's particularly bad when you pit two different languages against each other, because the APIs and patterns and idioms that work well in one might not be so elegant in the other. For example, writing my post with an early version of C# that lacked delegates would mean losing a lot of my cooler tricks.
So I got sucked in. I was bored, and having a beer with a lot of time to kill.
I feel like this is where we sit in the discussion:
"Here's my solution in VB6, it uses patterns A and B and I plan to extend it this way."
"I kind of like solving it this way in VB .NET, I can use patterns C and D to do the same thing."
"Ugh, no, you don't NEED OO code for this, I did it just fine with A and B."
"Whatever, patterns C and D are well-known and get the job done. Patterns A and B are known to have these problems, it's in plenty of texts."
"No, patterns A and B are perfect, it's C and D that have the problems. Please explain how you'd solve <intentionally bad C&D scenario> with C and D."
This is kind of the point where person 2 ought to roll their eyes and move on. VB6 has some weak spots and there will be problems it just can't solve in an elegant manner. VB .NET also has weak spots, and there will be tasks it fails at that VB6 can tackle elegantly. There's no really good general-purpose language that exceeds at all tasks, the best we can get is one that's great at some things and good at most. It is very possible that both people are right, are using perfectly fine approaches to the problem in their language, and would be up the unsanitary tributary if they tried that approach in the other person's language.
We mustn't get insulted when someone thinks another approach is better. If they're technically wrong, you can comment on that, but it doesn't need to be a "won" argument. Later, someone reading the thread can see a proposal->rebuttal->counterargument discussion and decide for themselves what is best for their solution. Heck, for almost every problem we've discussed I can come up with a scenario where the "good" advice is totally wrong. We need to know all solutions, with all their upsides and downsides, to make the best decisions based on our information, schedule, and budget.
Yeah, that's why I tried to stay out of it for... I think my last reply prior to this was somewhere north of page 10... it turned into dueling posts... as much fun as the Duck Pattern debate was, it degraded pretty fast... and no one seemed to want to back down. Olaf has his way of doing things that works for him great, more power to him. It doesn't work for me. But that doesn't mean he's wrong, nor does it mean I'm wrong. I'm not sure I'd follow DDay's pattern either (I think I attributed that right, I can't go back to the other page at the moment to look it up - if I get is wrong... mea culpa) doesn't make him any more wrong than Olaf either... I'll be the first to admit - I'm a bit jaded when it comes to VB6... I spent far too many years battling it, and yes, it was likely due to bad design on the system on our part... I won't fully throw VB6 under the bus, maybe just under the front wheels. And maybe if we had been willing to redesign our Duck, I'd have a more positive memory of VB6... but it left a bad taste in my mouth towards the end. I moved on to .NET (by choice) and never looked back. I'm happy where I am. I can understand some people willing/wanting to hang onto VB6 for as long as they can. It's a fine language, and there is a lot it can do. But to be honest, between the framework and the IDE, I'm far more productive in .NET than I ever was in VB6. Lambdas and LINQ alone are the two greatest things I use everyday that I don't want to think about having to solve in VB6. I'm not saying it would be impossible, it just would require a few more hoops for me to jump through.
That said, while I think it's perfectly fine for people to want to try to hold on to VB6 for as long as they can, I think they are doing themselves (and possibly others) a huge disservice by not expanding their skill set. It doesn't have to include .NET or JS... but they should be more than a one trick pony. That's where the real value comes in with any language. Olaf seems pretty knowledgeable on the inner workings of VB and the APIs and such .. and I suspect he has more in his toolbox than just VB6, and that's going to work out just fine for him... But I sometimes worry that his pro-VB6 advocacy, while respectable, is going to lead others to think that all they need to know is his vbRichClient and that it's going to work forever... but unless others have the same or similar understanding of the inner workings, it's going to fall apart when he gets hit by the proverbial beer truck (sorry Olaf).
Dang... I was going somewhere with this now I've lost it. It seemed like a good point though.
Oh well.
-tg
http://blog.jonathanoliver.com/why-i-left-dot-net
I've stumbled upon this link, and I haven't find anyone that posted it on this forum, so here it is. I would like to hear your opinions.
Well - he wanted to be corrected if he was wrong about the first common language runtime.
https://en.wikipedia.org/wiki/OpenVM...ge_Environment
C or BASIC or Fortran or COBOL and more - all from the same code.
This is a beautiful quote - I've lived the ASP.NET ViewState nightmare :eek:
The first statement though - there are so many great solutions out there.Quote:
There are so many great solutions out there. To assume that Microsoft has ordained The One True Way is absurd. If that was the case, we'd all still be using visual designers in Visual Studio to drag and drop button and link elements onto a WebForm surface and we'd wire up the button and rely on ASP.NET ViewState to help insulate us from the "horrors" of scary HTTP. The day I got rid of the last WebForm from one of my deployed code bases was a day of glorious celebration. Literally.
(And who ever thought "web controls" was a good idea? Apparently I did because I drank the Kool-Aid and embraced it to the fullest. It bit me hard. Ever seen a 2MB+ ViewState?)
Getting into a browser based GUI gave me tons of cool tools all with big communities that embrace them (my graphs are animated and I didn't write a line of that code - props to jqPlot).
I skimmed his "The Good" and mostly agree, though I don't have a lot of experience with all of the technologies. Obviously the real thing people want to argue about is "The Bad".
I think the thing that is most frustrating is the closed-mindedness he mentions, and it mostly causes every problem in his "bad parts". IMO, it's not .NET I want to leave, it's Windows. But I don't really believe that *nix is green pastures on the other side, it's just different brands of evangelism and religious devotion to particular toolchains.
But at least there's multiple *nix religions. The Church of Windows has no room for products that aren't blessed by the VS installer. Which is a pity, because TFS and MSTest are abominations, as are many software engineering tools once they're squeezed through the least-common-denominator filters of Microsoft's developers.
He's getting close.Quote:
This is kind of the point where person 2 ought to roll their eyes and move on
Olaf, the simple reason I haven't accepted your challenge to add new actions or persist data is that the strategy pattern is nothing to do with either of those things. Those are not the problems it's designed to solve. You might as well visit a car showroom and then, when shown Ford Pickup say "Ah but how many wash cycles will it take to get my whites whiter?"
The problem the strategy pattern is designed to resolve is that it allows any combination of behaviours to be applied to new Duck class without restriction from an inheritance tree.
I do think I was quite clear, way back in post 753, that I was demonstrating the strategy pattern. I was clear (and overly verbose, I suspect) in explaining exactly what problem the strategy pattern is designed to resolve. I was also clear when I said that VB6 cannot be used to implement the strategy pattern (and it can't). Finally, I think I was clear when I said "I'm sure you can find a way to work around that but I'm willing to bet you won't be able to without either violating the DRY principle or increasing the coupling between the classes. I'm willing to bet I'll see a pretty chunky Case or If statement appearing somewhere." Note that I did not say VB6 could not solve the problem, I said it could not implement the pattern.
Your current implementation does solve the problem but it does so in a way that both increases coupling and relies on a big switch. It also violates the Open Closed principle. You may not see the problem in that but some of us do and a quick google for "Low Coupling" and "Favour Polymorphism over Conditionals" and "Open Closed Principle" will quickly demonstrate that we're not alone. We're not making this up. It will also tell you why those things are considered good design principles far more eloquently than I can managed.
I won't comment any further on your implementation, though, as I don't think it's doing either of us any favours. I am going however going to carry on addressing some of the challenges you and others have raised.
In the interests of taking the example a bit further and implementing at least some of the features that have been discussed in this thread I thought I'd consider adding a new type of action to the structure (Post 758 - "introduce the new Property 'NursesYoung'"). I haven't gone with NursesYoung because the thread has moved on and introduced the concept of dancing ducks and that is much funnier.
I think the best way to handle that depends on why you're adding a new action. If you're a consumer of someone else's library but you want to extend it's behaviour I would recommend the Decorator Pattern which will let you extend the properties of just about any closed box. I think, though, that the scenario Olaf was considering was more akin to being the owner of the core product and wanting to extend it's functionality before shipping it to my customer base. In that scenario it's all about being able to ship reliable code.
To that end the primary concern, in my opinion, is not how many lines of code are required, but rather how easy it is for me to make a mistake and not notice. Ideally, if I make a mistake, I want to know about it at compile time. I don't want to accidentally forget to include one of the ducks in a switch statement (which is the human error that could be made in Olaf's implementation) or forget to set a behaviour in one of the concrete duck classes (which is basically the same human error that could be made with my previous example) and have that somehow get out the door. I want my design to police my code.
First of all, we want to set the default behaviours in the base abstract class. That way I can go to one place and add a new action. The constructor now looks like this:-
and we add a abstract method to make sure our derived classes do what they're supposed to and set their behavioursCode:public Duck()
{
SetDefaultFlyBehaviour();
SetDefaultQuackBehaviour();
}
All my derived classes immediately break. None of them has implemented those abstract methods so they no longer fulfil the contract laid out by the abstract class. They won't compile so there is precisely zero chance of shipping code at this point. The police are watching and I'm glad to have them there. Even better, the error list is presenting me with a nice to do list which will take me to each amendment I need to make merely by double clicking.Code:protected abstract void SetDefaultFlyBehaviour();
protected abstract void SetDefaultQuackBehaviour();
So now I need to start updating those derived classes. This isn't difficult, I simply move setting the default behaviour for each action out of the constructor and into an apropriate setter method. A mallard, for example, now looks like this:-
So now everything's working again and I know now that I'm being properly policed. Note that the work done so far is not to do with adding the action, it was to do with putting the police force in place. You'll only do it once and we can now proceed to add a new action with much more confidence.Code:public class Mallard : Duck
{
protected override void SetDefaultFlyBehaviour()
{
flyBehaviour = new FlapWingsBehaviour();
}
protected override void SetDefaultQuackBehaviour()
{
quackBehaviour = new QuackBehaviour();
}
public override void Display()
{
System.Diagnostics.Debug.WriteLine("I'm a Mallard");
}
}
We need a member variable for the dance behaviour (I'm actually going to make this a property to support Inversion of Control, more on that later):-
We put our policeman in place to make sure any derived classes behave themselves:-Code:public iDanceBehaviour danceBehaviour { get; set; }
We set the default behaviour in the constructor:-Code:protected abstract void SetDefaultDanceBehaviour();
And we add the action method:-Code:public Duck()
{
SetDefaultFlyBehaviour();
SetDefaultQuackBehaviour();
SetDefaultDanceBehaviour();
}
The error list is once again telling me that my derived classes are miss-behaving because they're not implementing the new behaviour setter so I can trivially visit each in turn to set it's default behaviour. And the best thing is, there is zero chance that I will accidentally forget one. More than that, when I ship this to my customers, the policemen I put in place will be watching over their shoulder too. If they've derived their own ducks then this is going to shout at them, in no uncertain terms that they need to specify the default behaviour for all their ducks.Code:public void Dance()
{
danceBehaviour.Dance();
}
NB. to anyone whose paying attention, yes this breaks backward compatibility but it does so deliberately. The assumption here is that whatever changes I'm making to my consumer rely on the new action being in place and I want to shout from the roof tops if it isn't.
NB. Also at this point I moved Swim from Inheritance to Composition. Inheritance does provide simplicity but the hunger in this thread seems to be for flexibility and far be it from me to disappoint.
The effort to add the new action really wasn't great. 3 lines of code and a trivial single line method added to the base and then allow the error list walk me through the changes I had to make to any concrete classes.
But having the confidence to ship isn't just about how I write my code. It's also about how I test it. I want to be using TDD for this stuff (and really should have been doing so already:eek:) and to do that I want to be able to test any possible combination of behaviours. I don't want to have to actually define a duck for every possible combination (a flying lead duck that floats seems unlikely to be needed... and potentially hazardous) but I want to be able to test that such a beast is possible just in case it's ever needed and for that I want Inversion of Control.
Inversion of Control is the principal that you don't ask an object what is, you tell it what you want it to be. In other words, although I might set default behaviours for my ducks, I want to be able to tell a Rubber Duck to damn well sink if I want it to.
There are two basic ways of achieving this, you can implement a constructor that receives the desired behaviours as parameters and overrides the defaults or you can offer public setter methods. The primary difference between the two is that the former method makes immutable objects while the latter makes mutable ones. There a load of raging debate about which is better and the trend seems to be toward immutable at the moment but I'm a bit old school and prefer mutability so I'm going with the second option. The implementation is simple and you've already seen it, simply change the behaviour member variables in the base class to be public properties:-
I can now create any combination of duck I want. My Sinking, Squeaking, Flying, Lead Weighted fan of the Polka can become a reality. And if the consumer of my duck simulator wants a rubber duck to learn to paddle at run time it can.Code:public iFlyBehaviour flyBehaviour { get; set; }
public iQuackBehaviour quackBehaviour { get; set; }
public iDanceBehaviour danceBehaviour { get; set; }
public iSwimBehaviour swimBehaviour { get; set; }
Of course, I put all this in place because I wanted to enable testing and I really want to be able to text my duck behaviours independently of the breed or manufacture. It just doesn't seem instinctive to test a squeaking mallard so I'll create a mock duck that has no defaults:-
I've chucked this in the main project for now, just for convenience, but realistically this would probably go in a separate project. I can do that because I obeyed the Open Closed PrincipalCode:public class MockDuck : Duck
{
protected override void SetDefaultFlyBehaviour()
{
flyBehaviour = null;
}
protected override void SetDefaultQuackBehaviour()
{
quackBehaviour = null;
}
protected override void SetDefaultDanceBehaviour()
{
danceBehaviour = null;
}
protected override void SetDefaultSwimBehaviour()
{
swimBehaviour = null;
}
public override void Display()
{
System.Diagnostics.Debug.WriteLine("I'm a Mock");
}
}
Another challenge that has been made is the ability to add new ducks and new behaviours. Well that easy because they're completely decoupled from the core of the design. Want to add a new duck? Just derive a new duck class. Want to add a new behaviour? Just implement a new derivative of the appropriate behaviour interface. This really couldn't be easier.
I wasn't paying attention when I implemented one of the duck classes. See if you can see what I got wrong.
Attachment 129377
@Sitten I haven't implemented flyable, quackable etc interfaces here because I'm describing a world where all ducks share the same actions, if not behaviours. It's a perfect complement to the Strategy pattern, though, and one I've used often. The strategy pattern allows you to have dynamic behaviours for the actions while the interfaces police that nothing asks something to carry out an action it doesn't understand
@Olaf I haven't implemented persistence because it was so far off the radar from what I originally intended to demonstrate and writing this up took plenty enough time already. I may or may not get around to it in the next few days. I'm supposed to be giving the house a coat of paint ready for some viewings by potential renters and I really should be focussing on that.
I'm curious as to why you think it's difficult though. It's just be a matter of taking Display out of the derived classes and changing it to a method in the base Duck class which accesses a resource based on the derived type. If you think your design offers something that mine doesn't in this area then go ahead and explain what. I'll try and match it.
The one last think I'd challenge you on is something you asked me to in post 791:-
"Please give an example for such an extension as a clearly formulated change-request.
After that let the both of us implement that request and compare notes - it really is that simple."
Here's the scenario: I do not have access to your source code, only your compiled library. Please allow me to implement a new type of duck.
Don’t’ want to disturb your “duck hunting” experiance . Its getting more and more interesting since some of you are ready to name as “animals” even the duck-looking-stones.
If you have any reasons that make you believe that you could it better , (with sarcasm) please , just please , be my guest.Even I have to wait the “duck season” to be finished.
But before than that , let me inform you about something.
I totally respect those 300-400 persons I see every time I loggin , searching something about v*.net.
I also have no respect for any p***** ( 20-30 , no more) that says to me what I sould have done.
As a dino or caveman or what ever.
I just laugh at them.
And sorry , but that includes you as well.
Mikisoft i am almost certain you have posted a link to that exact same article before as i have read it before and i believe commented on it then but i will do so briefly again.Quote:
http://blog.jonathanoliver.com/why-i-left-dot-net
I've stumbled upon this link, and I haven't find anyone that posted it on this forum, so here it is. I would like to hear your opinions.
The article is piss poor in my opinion as the article title bears no relation to all the things he wants to leave behind!!
What it should be titled is "Why i am leaving Windows" as much of his arguments are to do with this then .NET itself, and even then i would say his article is poor as he even admits in most cases there are choices but because what he sees as the default choice is not to his liking then its BAD !!!
Also a bunch of his other arguments are to do with things that have absolutely nothing to do with the language, for instance choice of database or Source control. These things are a Choice, you have numerous choices also Source Control like GIT is available on windows and he has the choice of any number of databases he could use.
Its a bit like saying your leaving VB6 because Access is crap.
Some of his other arguments are equally as bad he say one of his other problems is "C# strongly encourages the use of locks and mutexes" and then goes on to say - "Even if I don't have any concurrency in my code, the default and preferred method is using locks."
So he is basically saying you can doing something badly using .Net and although i don't use it the fact that you can is a bad bad thing!!!
Pretty much all his arguments are like that they are terrible terrible arguments and it made me annoyed just reading it.
Now can we please move back to the Ducks, that discussion has been much more informative and entertaining !!!
NeedSomeAnswers, yeah that article looks for me familiar too, but I can't find that I or someone posted it here. Tried again to search now and it returned me nothing.
BTW. One more article: https://adtmag.com/blogs/dev-watch/2...successor.aspx
Looks like there is already a successor to VB6.
"There is a very large community of developers that grew up on VB6 and are still looking for a worthy substitute," said Erel Uziel, CEO of Anywhere Software. "Their voice is loud and clear - they want a modern RAD tool that will let them build real-world desktop and server apps without the hassles and complexity of existing programming languages. B4J provides an easy-to-use environment that lets developers get the job done."
Quote:
Originally Posted by sten2005
From what I've seen there's a few potentrial successors around. The feature that often seems to be missing is the ability to load up/port existing VB6 projects and that's feature that most of the VB6 users seem to really want. I couldn't tell from that article whether B4J supported that or not.Quote:
Looks like there is already a successor to VB6
I'm a little surprised someone hasn't built a conversion... the project should be easy, most basic forms too... the complexity comes when you've used a thrid-party control of some kind.
-tg
That's a shame. It's the one killer feature that all these offerings seem to lack.Quote:
it can't load or port existing VB6 projects
I'm guessing it's more difficult than it sounds. I know Olaf was talking about it in one of AxisDJs threads and he seemed to have ideas that would cover most of it but he was kicking it into the long grass while he worked on other things.Quote:
I'm a little surprised someone hasn't built a conversion
@MikiSoft,
That's Java, how could it be a VB6 replacement?
To be a VB6 replacement (or "a new VB6", as the thread tile suggests) would have to be VB6 + some of the things that it lacks, imho.
But unfortunately as we know, there will never be a language and architecture like VB6. Regarding that, B4X is a good option and IMHO it's more similar to VB6 than VB.NET will ever be, since it is built by a VB6 enthusiast who knows what's good and noticeably had the same problems like us.
Here's a rough outline of a post I spent way too much time today on and couldn't finish. Weird food for thought.
- VB6 was successful because Windows was the dominant business OS and it simplified client development enough to create a wider field of developers than WinAPI alone created. It also introduced RAD tools, and between those and the wider RAD methodology every language felt its impact. It changed expectations about how quickly software could be prototyped and delivered.
- VB has always been a Trojan Horse designed to keep developers tied to the Windows platform.
- VB6 and its runtime was coupled to the Windows API. This was especially true of the UI tools, which are really facades over GDI.
- VB .NET isn't much better. While we have a working CLR on all platforms, WinForms itself is so GDI-centric it's easier to make a new framework like Gtk# than port it. Incidentally, those frameworks run on Windows.
- No relevant number of people cared about this coupling because there was no relevant competition or demand for clients on platforms other than Windows.
- Windows as a platform is endangered.
- HTML is far more capable as a platform, and most people feel its shortcomings are mitigated by the fact that every relevant OS *must* have a browser capable of supporting HTML's features.
- MS noticed this, it shows in their abandonment of WinForms for a plethora of XAML infrastructures: XAML has almost no Windows-centric assumptions, though WPF is still so Windows-centric it's unimplemented anywhere else.
- But the many versions of the CLR behind each XAML-based framework all had tiny incompatibilities. It was very clear that the Windows API did NOT port well to non-Intel, non-PC devices.
- Win10 looks like it might be unified across all devices, but if no one's buying Win10 PCs, tablets, or phones, who cares?
- MS seems to be betting against a long-term Windows dominance.
- Office runs on the web, iOS, Android, Mac OS, Linux...
- VS Code is an IDE that supports non-MS languages on Mac OS and Linux.
- VS cloud services performs many Windows-centric tasks off-board, meaning you don't need a Windows PC to compile, test, etc.
- One of VS 2015's new features is the ability to compile Objective C projects, formerly a task only implemented by XCode. There's no Obj-C libraries or tools that target Windows. Hmm.
So IMO, if we want something to make a splash like VB6 did, it's almost silly to say it would focus on Windows Clients. It needs a UI framework that can target all or most of the relevant platforms: iOS, Android, and Windows at minimum. That UI framework needs some mechanism that connects it to the code, which must run on all of the same platforms. I'm very, very skeptical that this answer will come from Microsoft while they're still interested in creating Windows Client developers. And I think anyone who wants to bet their future on Windows Clients has very low aspirations.
Some solid points there.
One thing sorely missing among most of the multiplatform development stacks though is a RAD-supporting IDE for GUI applications, from stand-alone to database client to general client-server client. That's VB's strength (and probably VB.Net's, C#'s, etc. as well). I doubt we'll see much like it again, since it isn't just about the IDE anyway.
The .Net crowd attributes far too much of this to Visual Studio. I think more of the credit goes to UI infrastructure that in VB is integral and extensible, in .Net less integral but still executed well, and in 3rd party stacks (and egad even more so in open source stacks) is almost always an afterthought. Yes, there are exceptions but they're rare and not what would be considered mainstream (REALBasic, Delphi, etc. are deader than VB6 for all practical purposes).
B4J suffers from this syndrome, stuck with the possibly-to-be-orphaned JavaFX which in any case often requires markup-level fiddling and tuning.
I'm not sure I feel the same rad-way about VB6.
When I first started down the VB6 path one of my coders was very much into creating a .FRM for each client-facing maintenance screen. That one or two weeks of coding wasn't acceptable to me - maybe you guys code incredibly fast in VB6 but my coders didn't seem to be headed that way.
So I did what I've done for decades earlier on prior platforms - abstracted myself away from the UI form. Simple table in your database telling the UI form what to put up - a text box here - a label here - some radio buttons here. Now I've got database binding made easier - and a single event can handle ANY textbox losing focus. Actually since you had to have a floating textbox for FLEXGRID editing I made all my textboxes really labels and floated that same textbox onto the form for editing.
Now a customer facing maintenance form can be created in hours. That is RAD to me. And that form can change and the user see that change without leaving the app. That's maintenance-made-simple-gone-wild.
I've created that same environment in JavaScript with jQuery - DOM-created on the fly by loading up tables in the database with screen metadata.
That's RAD to me - but I'm just a boring database programmer.
Maybe a form for theatre lighting controls is way different (as one of the VB6 people here was bound to VB6 by).
Where do you all get your RAD experience with VB6? What kind of FORMS are you creating??
szlamany, I guess I just kind of wonder what the heck your developers were doing that took months to make one form, unless you're confusing the whole of development effort with form development?
I've heard of such "UI in the database" frameworks before, but I'm skeptical. You have to develop more or less your own markup language, and an application that can interpret it. If you're using HTML I guess that helps, but how do you version HTML within a database column? What's the automated testing plan?
I guess I'm just confused. What's a 'user-facing maintenance screen'? What were its functions, that took people weeks to finish? Given a random database schema and an IDE I'd expect to have a functional CRUD app in 30 minutes if I'm really grinding away, what's the complexity I'm missing? (I always worry this kind of question sounds snarky, I'm not being sarcastic!)
dilettante:
I never really associated it with VS as much as I did GDI. GDI was developed because GUI is what sold Windows, but deep integration between languages and UI isn't the sole domain of the "Visual Whatever" line. Obj-C and Cocoa were made for each other, Java's got 2 terrible UI frameworks, and GTK#'s what you get by with when doing C# on Mono. Most of these don't port well, because they are strongly coupled to an OS's system calls as opposed to being abstractions of a generally useful graphics API. XAML is a step in the right direction but again far too hitched to the Microsoft APIs to make for a reasonable port.
HTML's pretty much the only platform-agnostic UI framework we have. It makes me sad.
I said one or two weeks per form (not months) - I felt that was excessive. Maybe an attendance entry form for teachers or a form for loading invoices against PO's. More then just CRUD.
At any rate - it wasn't my intent to elaborate on my methods - I wanted to hear what people were making in VB6 and how the RAD-ness of it was so important.
I can appreciate that when I want to write a down and dirty little textimport program it's VB6 that makes that in 3 lines or less - imo. I've only created one large VB.Net heavy-UI app and it was sluggish - and the RAD-ness of the development seems to go down as the IDE gets more complicated - and yup that .Net IDE is a beast.
Although I do have one 5-project solution that mixes VB.Net and C++ and the .Net IDE allows effortless multi-project debugging. I would have been scared of doing something like that in the VB6 IDE.
Qt is a solid framework, and its "signals-and-slots" construct is a natural fit if you're accustomed to .NET's events and delegates. QtQuick (and QML, its Javascript-like declarative UI language) is solid, too.
The main "difficulty" with Qt, if you want to call it that, is that it's all very C++ oriented. There are other bindings, but none are as stable as the default C++ ones.
But if you're comfortable with C++, I personally don't think there's a better cross-platform UI framework.
I guess I'm a bad person to ask. I've kind of inverted my workflow so I design UIs either last, or alongside code features. I'm starting to think the design-first nature of VB is harmful to people who want to take on medium-scale to large-scale projects. There's a stereotype of VB projects where they're just 40,000 line Form1.vb files, and skimming over a bunch of newbie projects doesn't do anything to combat it. I get the feeling a lot of people spend their career without the revelation that an event handler can call a helper method. And I wish I was being sarcastic when I said the majority of .NET developers seem to think creating a new class is a task ONLY for Martin Fowler-level certified architects. More and more it feels like Windows really does make people stupid.
But in less designer-oriented languages, it seems like there's more of an emphasis on separation of concerns from the start. There's some bad outliers, like PHP, but then there's some good ones, like Rails. On one end you have a language that hopelessly couples your UI and logic, and on the other one that can't function if you couple them. PHP has a REALLY bad reputation. Rails, not so bad.
I think UI is like an iceberg: I spend 90% of my work on the backend code, and it feels like the UI's just a mask I glue on it. It's only tricky if I'm asking for something that's not in the toolkit, like a knob control or a complex graph. Even then, I'm not using a designer, I'm writing 2D drawing code. The past few years, I'm not sure if I've used a designer at all outside of really quick example projects.
I didn't make good HTML pages when I used designers back in the day, I just spent less time thinking about HTML. If I had to come back and style them and make them work in multiple environments, I bet overall I'd have an easier time starting from scratch than trying to work around designer-generated code. But then, when 99% of my HTML pages are just plain text with a smattering of images, I don't really care if my backend code isn't optimized for extensible styling via CSS, do I?
Maybe that's what I figured out. Designer-focused RAD is great for small projects with well-understood requirements and easy logic. But at some level of complexity, you're spending so much more time on the backend than the UI you don't really care if you have a designer. In fact, because on this project we have a person with the title "designer", the UI is mostly picked out for me, I just have to make sure I have a way to glue it to the code I write. That glue is mostly boring to write, once you know a few tricks.
Actually design-first doesn't work well in VB6 either, if you mean UI design.
Most of the power tools for building RAD forms in VB6 require that the database and the queries and stored procedures all be designed and implemented up front. Then the IDE can extract database metadata to quickly build forms from that.
The most primitive example is the Data Form Wizard add-in. More powerful examples are all of the RAD features in the DataEnvironment, Data View Window, etc. Sadly most self-taught plinkers have no idea how to even begin to use these, which explains why so many never could pass the old VB6 MCSD exams.
If you have never had the IDE make a database connection at design-time, you don't know VB. It offers a far more holistic approach to development than the creay old "dark world" approach of command-line script with a GUI tacked on as an afterthought.
That sounds so Access like to me
It sounds like my first dang VB project to me, and while the word CRUD certainly applied to that project I'm pretty sure it wasn't in the way the tool designers intended. It's hard for me to really work that angle though, as it was my first-ever VB project and far outside the complexity of what you should throw at such a greenhorn.
dilettante, I totally believe it for a single-table database, or maybe a 2-3 table one with some relations. I can imagine it making some samey UIs which is a good thing, you only need to train users once. For a particular and very, very common kind of CRUD app that is precisely what the doctor ordered. But beyond that it gets weird to me. Maybe it's because my project is a GPS navigation tool, and there's no way I could ever be binding to something as simple as a handful of textboxes or a datagrid. I can be outside of the use case of those wizards without declaring them useless.
I had some bad dang experiences with wizards that tried to connect my UI to my code. It didn't do well at all if I didn't want that samey interface. If we're narrowing the discussion to that context, we don't need anything but VB6's tool suite, sure. But I'm considering the entire breadth of application development. I'm talking about applications like Google Maps, Skype, Twitter, Netflix, Facebook... things people get excited about using, rather than get paid to use. I want my "new VB6" to make kids want to get into programming early, and I can't think of any age that gets stoked for writing customer invoice systems.
I don't know, I keep getting hung up on your condescension. For a while it was cute, but it's far less effective than practical discussion. If I have to use code wizards to be a "real developer" then call me Pinocchio, I'm doing really cool things with my fake skills. I think a relevant language will have to think outside the enterprise CRUD app box if it's to compete today, but if it happens to serve that niche too I'd like it as well.
SS - you have to get past the condescension (hope you weren't talking about me).
Sometimes I think it's directed at me specifically! Am I "dark world"? Would that be bad?
Dil - you said you taught? Gotta build a person up man!
Steve, I've heard you mention your "DB defines the UI" aproach before and I do find it intriguing. I'm not sure it would be my preferred aproach (though I'm open to reconsidering that) but I can see how it could seriously enable RAD, particularly with simple CRUD apps. Holding the data in the DB along with meta data that defines a UI and how it should bind to that data sounds like an elegant solution which would enable rapid development and simple roll out.
I'm intrigued as to how you handle complex behaviour though. You know, the complex business logic that many of us would hive off to some class or other. Do you somehow hold that logic in the DB too?
I'm aware that by referring to "simple CRUD apps" it might sound like I'm demeaning them. I'm not. Simple CRUD apps probably represent 90% of the business app that get produced and they've certainly been the vast majority of the applicatiosn I've worked on in my career. Anything that facilitates their development can only be a good thing.
Now that's not the experience i have had, but then my experience is mainly working in software houses with a number of development teams. In that environment you cant hide behind bad code practices, you get found out. Code is reviewed and developers are expected to know how to develop properly.Quote:
And I wish I was being sarcastic when I said the majority of .NET developers seem to think creating a new class is a task ONLY for Martin Fowler-level certified architects. More and more it feels like Windows really does make people stupid.
Maybe you have just been unlucky.
I did work in a place earlier in my career in which the developers worked in there own little silos with zero knowledge sharing and they tend to be not very healthy places to work and places like that tend top store up maintenance issues in there software so the developers spend much of there time just fixing bugs in unstable systems than introducing new features.
It's not experience I've had since I moved to C# but it pretty much describes my experience back in my VB.Net days (and in my VB6 days before that).Quote:
Now that's not the experience i have had
I think it's cultural and the culture stems from the 90s. Languages like Java and C++ were "proper" languages and VB6 was a "Toy" language (nb. not my opinion, I'm just describing the prevailing opinion of the time) and, as such, big software houses used Java and C++ while VB tended to be used by smaller software houses or IT departments in larger companies.
When .Net came along C# tended to be adopted into the large software house and VB.Net tended to be adopted into the small ones. There's no good technical reason for that, just that cultures tend to be slow to change. If you're in a large software house you do tend to work in teams with other developers while in a small one it's likely that you're the sole developer on the products you support and Rodney in the next cubicle is probably working on something entirely different. You might share a coffee in the morning but you probably don't touch each others code much so you tend not to get into the sort of technical discussion that promote learning as often. You're also probably under much more pressure to "just get it working now".
Having worked in both VB.Net and C# I do see some evidence that C# developers tend to be more interested in good development practices and software engineering while VB.Net developers tend to be more focused on delivering a quick solution that will work for now. This is a sweeping generalisation, of course, and an unfair one in a great many cases. There are, of course, many large VB houses and small C# ones.
I also don't think that this distinction is based on ability. Take any half decent VB.Net developer and expose them to a large software house with collaberative teams and they'll be talking patterns pretty quickly. Take any C# developer and put them in a small company where they only work on their own code and where the customer repeatedly rings them direct to tell them to ammend a bunch of data directly in the production database and I'm willing to bet you'd see them fall back on quick and dirty hacks that let them ship quick before you know it.
I am not sure if it's an Intellectual Property but I have always been intrigued by the method being used by sz, I think he may have picked up some ideas from mendhak in their conversation before, I was hoping sz could share a very simple CRUD using one table to showcase how his technique works. :)
Here you go dee
This is a simple READ SPROC that returns all the USERS of the system
You can see what the first SELECT returns.Code:Create Procedure awc_StaffUsers @Staff varchar(100), @UserId varchar(100), @username varchar(100)
As
Set NoCount On
If IsNull(@UserId,'')=''
Begin
Select '["edit","reader","print"]' "boottab"
,'[]' "operators"
,'UserId' "editkey"
,'{"tabfulldesc": true}' "awcoptions"
,'Manage Users' "heading"
,'Users' "tabcaption"
Select AWCElement "acs-work"
,AWCOptions "options"
From AWCConfig_T
Where AWCType=5 and AWCFlag1='awc_StaffUsers'
Order by AWCSeq
End
Select AW.UserId, AW.AKA, AW.Employee...
from AWCUser_T AW
.
.
.
Where IsNull(@UserId,'')='' or AW.UserId=@UserId
Order by 1
The second select feeds a SlickGrid with grid/column meta data - that looks like this (that meta data is required by a SlickGrid plug in that I use):
The third SELECT returns data for the GRID. Notice the WHERE clause. This SPROC is designed to be called for a single ROW also (when that ROW gets edited by a DETAIL PANEL that comes up in front of the GRID).Code:acs-work options
slickgrid {"editable": false,"enableAddRow": false,"enableCellNavigation": true,"asyncEditorLoading": false}
col1 { "id": "Employee", "name": "Employee", "field": "Employee", "width": "80", "sortable": "true", "cssClass": "cell-title" }
col2 { "id": "EmpName", "name": "Employee Name", "field": "EmpName", "width": "150", "sortable": "true", "cssClass": "cell-title" }
col3 { "id": "AKA", "name": "AKA", "field": "AKA", "width": "50", "sortable": "true", "cssClass": "cell-title" }
col4 { "id": "EmpType", "name": "Type", "field": "EmpType", "width": "50", "sortable": "true", "cssClass": "cell-title" }
col5 { "id": "EmpHours", "name": "Hours", "field": "EmpHours", "width": "60", "sortable": "true", "cssClass": "cell-title" }
col6 { "id": "EmpStatus", "name": "Status", "field": "EmpStatus", "width": "100", "sortable": "true", "cssClass": "cell-title" }
col7 { "id": "Title", "name": "Title", "field": "Title", "width": "150", "sortable": "true", "cssClass": "cell-title" }
col8 { "id": "RoleAdm", "name": "Adm", "field": "RoleAdm", "width": "50", "sortable": "true", "cssClass": "cell-title" }
col9 { "id": "RoleMgr", "name": "Manage Tasks", "field": "RoleMgr", "width": "110", "sortable": "true", "cssClass": "cell-title" }
col10 { "id": "RoleUsr", "name": "General User", "field": "RoleUsr", "width": "100", "sortable": "true", "cssClass": "cell-title" }
col11 { "id": "RoleSp1", "name": "Employee Default", "field": "RoleSp1", "width": "130", "sortable": "true", "cssClass": "cell-title" }
col12 { "id": "EMail", "name": "EMail", "field": "EMail", "width": "225", "sortable": "true", "cssClass": "cell-title" }
col13 { "id": "LoginEnabled", "name": "Login Enabled", "field": "LoginEnabled", "width": "100", "sortable": "true", "cssClass": "cell-title" }
col14 { "id": "LoginStatus", "name": "Login Status", "field": "LoginStatus", "width": "250", "sortable": "true", "cssClass": "cell-title" }
And that draws a screen in my web app like this (using a jQuery SlickGrid library that is so, so cool). In a labor union customer I have I can returns 100's of thousands of rows to feed the SlickGrid - VB6 FlexGrid's used to tank when I returned lots of rows.
Next post I will show you the detail panel that opens up when you click that OPEN FOLDER looking button...
Here is a READ SPROC for the DETAIL panel. Any SAVE operation that happens always re-executes this READ sproc to return the "saved data" from the DB.
Note that is also detects the lack of a record (as in we must be adding) and returns a nice default recordset to the web appCode:Create Procedure awc_StaffUsers_Edit @Staff varchar(100), @UserId varchar(100)--, @username varchar(100)
As
Set NoCount On
Declare @FromSave int
Set @FromSave=0
If Left(@UserId,6)='~save~'
Begin
Set @FromSave=1
Set @UserId=Right(@UserId,Len(@UserId)-6)
End
If @FromSave=0
Select '["save","add","undo"]' "buttons"
,'{"fixedheight": 475, "fixedwidth": 993}'
"awcoptions"
Select Cast(AW.UserId as varchar(100)) "UserId"
,EM.AKA
.
.
.
,EM.EmpType
,EM.EmpHours
,Case When IsNull(UR1.RoleLevel,'')='A' Then 'Y' Else '' End "RoleAdm"
,Case When IsNull(UR2.RoleLevel,'')='A' Then 'Y' Else '' End "RoleMgr"
,Case When IsNull(UR3.RoleLevel,'')='A' Then 'Y' Else '' End "RoleUsr"
,Case When IsNull(UR4.RoleLevel,'')='A' Then 'Y' Else '' End "RoleSp1"
,Case When IsNull(UR5.RoleLevel,'')='A' Then 'Y' Else '' End "RoleSp2"
,Case When Exists(Select * From AWCUser_T AU Where AU.Username=EM.Employee and LoginType='standard') Then 'Y'
Else 'N' End "LoginEnabled"
,(Select Top 1 '"'+LA.ActivityType+'" '+LA.ActivityText + ' ' + Convert(varchar(23),LA.ActivityDate,121) From AWCUser_T AU
Left Join AWCLoginActivity_T LA on LA.UserId=AU.UserId
Where AU.Username=EM.Employee and LoginType='standard'
Order by LA.ActivitySeq Desc)
"LoginStatus"
Into #XYZ
from AWCUser_T AW
LefT Join Employee_T EM on EM.Employee=AW.UserName
Left Join User_T UT on UT.UserName=AW.UserName
Left Join UserRole_T UR1 on UR1.UserId=UT.UserId and UR1.RoleId=1--select * from role_t
Left Join UserRole_T UR2 on UR2.UserId=UT.UserId and UR2.RoleId=2--30001
Left Join UserRole_T UR3 on UR3.UserId=UT.UserId and UR3.RoleId=3--30010
Left Join UserRole_T UR4 on UR4.UserId=UT.UserId and UR4.RoleId=4--30101
Left Join UserRole_T UR5 on UR5.UserId=UT.UserId and UR5.RoleId=5--30102
Where AW.UserId=@UserId
And this is the screen that appears. Now this HTML I do have to feed to the web app - I'll show that in the next post...Code:If @@RowCount=0 Insert into #XYZ values (@UserId,'','A'
,'','',''
,''
,'',''
,''
,''
,'','','Y','',''
,'Y','* New User being Added *')
Select * From #XYZ
Drop Table #XYZ
Go
This is what the HTML looks like for that detail panel. This HTML loads via an AJAX call - you can even execute the AJAX yourself so that when you are modifying these screens you don't have to re-login to the web app to get fresh HTML's for the detail panels.
notice those AWC- class names above - they facilitate the binding action to the DB. Note the AWC-CTRLVAL# class as well...Code:<div id="acs-edit-staffusers" style="padding-left: 10px; padding-top: 10px;">
<div class="acs-edit-lefthalf">
<div class="acs-div-p">
<span class="acs-span-xxlarge">Username</span>
<input type="text" class="awc-EmployeeEdit acs-edit-medium-text"/>
User Id: <label class="awc-UserId awc-ctrlval2 acs-edit-small-text"></label>
<br />
You also see that each element has an ACS-xxxx CSS that gives me some standards in the HTML look - I know boring DB stuff...
Radio button data binds to a textbox with the CSS ACS-EDIT-HIDDEN-TEXT (above) - which of course hides the data - and also BINDS it all to the RADIO buttons.Code:<span class="acs-span-xxlarge">Status</span>
<input type="radio" class="awc-EmpStatus" name="awc-EmpStatus-radio" value="A" /><span class="acs-span-medium-left">Active</span>
<input type="radio" class="awc-EmpStatus" name="awc-EmpStatus-radio" value="I" />Inactive
<input type="text" class="awc-EmpStatus acs-edit-hidden-text"/>
Ok - here are two buttons that call SPROC in the database - and those AWC-CTRLVAL# items are passed to that SPROC callCode:<br /><br />
<span class="acs-span-large">EMail</span>
<input type="text" class="awc-EMail acs-edit-xlarge-text"/>
<br /><br />
<span class="acs-span-large">AKA</span>
<input type="text" class="awc-AKA acs-edit-medium-text"/>
</div>
<div class="acs-div-p">
<span class="acs-span-heading acs-span-large-left">User Login</span>Initial Login will be enabled upon clicking SAVE
<br /><br />
<div class="acs-edit-side-x-side">
<div class="acs-edit-side-x-side-left-full">
User Login Enabled:
<label class="awc-LoginEnabled"></label>
<br /><br />
Status:
<label class="awc-LoginStatus"></label>
<br /><br />
<button type="button" class="acs-sproc-button" acssproc="EMailLogin_Setup 2" title="Disable EMail Login">Disable Login</button>
<button type="button" class="acs-sproc-button" acssproc="EMailLogin_Setup 1" title="EMail Invite">Re-enable</button>
</div>
The really cool thing about that SPROC button is first, obviously, it make an AJAX call. What comes back in JSON is a whole set of instructions that can hide buttons and update fields on the screenCode:<div class="acs-edit-side-x-side-right-full">
To clear Invalid Password attempt<br />Lockout click button below
<br /><br />
<button type="button" class="acs-sproc-button" acssproc="EMailLogin_Setup 3" title="Clear Invalid Password attempt Lockout">Clear Lockout</button>
<br /><br />
</div>
</div>
</div>
</div>
<div class="acs-edit-righthalf">
<div class="acs-div-p">
<table>
<tr>
<td>
<span class="acs-span-heading">Permissions</span>
</td>
<td>
<input type="text" class="awc-ContactType acs-edit-hidden-text"/>
<input type="checkbox" class="awc-RoleAdm" /><span class="acs-cblabel-xlarge">Administrator (*)</span>
<br />
<input type="checkbox" class="awc-RoleMgr" /><span class="acs-cblabel-xlarge">Manage Tasks</span>
<br />
<input type="checkbox" class="awc-RoleUsr" /><span class="acs-cblabel-xlarge">General User</span>
<br />
<input type="checkbox" class="awc-RoleSp1" /><span class="acs-cblabel-xlarge">Employee Task Default</span>
</td>
</tr>
<tr>
<td>
</td>
<td>
<br />
* Check all that apply
</td>
</tr>
</table>
</div>
<div class="acs-div-p">
<span class="acs-span-xxlarge">Employee Type</span>
<input type="radio" class="awc-EmpType" name="awc-EmpType-radio" value="FT" /><span class="acs-span-medium-left">Full Time</span>
<br />
<span class="acs-span-xxlarge"></span>
<input type="radio" class="awc-EmpType" name="awc-EmpType-radio" value="PT" />Part Time
<input type="text" class="awc-EmpType acs-edit-hidden-text"/>
<br /><br />
<span class="acs-span-xxlarge">Hours per Day</span>
<input type="radio" class="awc-EmpHours" name="awc-EmpHours-radio" value="8" /><span class="acs-span-medium-left">8</span>
<br />
<span class="acs-span-xxlarge"></span>
<input type="radio" class="awc-EmpHours" name="awc-EmpHours-radio" value="7" />7
<br />
<span class="acs-span-xxlarge"></span>
<input type="radio" class="awc-EmpHours" name="awc-EmpHours-radio" value="6.5" />6.5
<input type="text" class="awc-EmpHours acs-edit-hidden-text"/>
</div>
</div>
</div>
I showed you simple - that was a simple screen with just a couple of buttons.
Below is where the cool stuff happens - in my meta data for the detail panel for instance - this one is for a task based tracking system. I can add new features through this metadata and make the JavaScript do whatever I want whenever - not tied to what ASP.Net decided I can do...
Notice the VIEW element in that JSON - and tons of other little aspects of how I want the UI to behave.Code:If @FromSave=0
Select '["save"'+Case When @FromEmpty=0 Then ',"add"' Else '' End+',"undo"]' "buttons"
,'{"fixedheight": 475, "fixedwidth": 993'
+', "reseteditkey": "TaskId", "view": '+@View
+Case When @FromEmpty=1 Then ', "adddirty": true' Else '' End
+Case When @FromAdd=1 Then ', "saveoptions": true, "nomsg": true, "resetview": true'
+', "noclearsave": true' Else '' End+'}'
"awcoptions"
This task system is very complicated UI. Notice all the acs-edit-view-x CSS tags - that controls what panels appear based on what type of TASK you are creating.
This is the VIEW that appears when you click the ADD button to start a new task - basically VIEW 0. Notice the HTML for VIEW 0 above only has two elements - a textbox with class acs-class-datepicker (which makes that nice jQuery date picker appear in the html). And that other element just called awc-addtype (and that class it has called acs-divmulti). The SPROC actually returns HTML in that field which gets loaded onto screen. The acs-divmulti tags is used in the JavaScript to help fill that field and also read from it when SAVE's are done.Code:<div id="acs-edit-tasktasks" style="padding-left: 10px; padding-top: 10px;">
<div class="acs-edit-full">
<div class="acs-div-p acs-edit-view-0">
Select a Task Type below and then click the SAVE button above
<br /><br />
<table>
<tr>
<td>
<span class="acs-span-medium">Initial Date</span>
<input type="text" class="awc-InitialDate acs-class-datepicker acs-edit-small-text"/>
</td>
<td>
<div class="awc-addtype acs-divmulti"></div>
</td>
</tr>
</table>
<br />
</div>
<div class="acs-div-p acs-edit-view-1 acs-edit-view-2 acs-edit-view-3 acs-edit-view-4">
<div class="floatleft">
<span class="acs-span-small">Task:</span>
<label class="awc-TaskType acs-span-giant-left acs-edit-important"></label>
<span class="acs-span-large">Task Id:</span>
<label class="awc-TaskId acs-edit-small-text"></label>
<label class="awc-IdTaskEstab acs-edit-hidden-text"></label>
<label class="awc-IdLocation acs-edit-hidden-text"></label>
<label class="awc-IdComplaint acs-edit-hidden-text"></label>
<label class="awc-IdTaskOwner acs-edit-hidden-text"></label>
<label class="awc-IdSanitarian acs-edit-hidden-text"></label>
<br />
<br />
<span class="acs-span-xlarge">Initial Date</span>
<input type="text" class="awc-TaskDate acs-edit-small-text acs-class-datepicker"/>
<input type="radio" class="awc-TaskStatus" name="awc-TaskStatus-radio" value="Assigned" />Assigned
<br />
<span class="acs-span-xlarge">Completed Date</span>
<input type="text" class="awc-TaskCompleted acs-edit-small-text acs-class-datepicker"/>
<input type="radio" class="awc-TaskStatus" name="awc-TaskStatus-radio" value="Completed" />Completed
<input type="text" class="awc-TaskStatus acs-edit-hidden-text"/>
</div>
<div class="floatleft">
<span class="acs-span-medium">Note</span>
<textarea class="awc-TaskNote acs-edit-large-text-box"></textarea>
</div>
<div class="acs-edit-view-1 acs-edit-view-2">
<div class="floatleft clearfix">
<div class="acs-edit-view-1">
<span class="acs-span-medium">Category</span>
<input type="radio" class="awc-TaskCategory" name="awc-TaskCategory-radio" value="Residential" />Residential
<input type="radio" class="awc-TaskCategory" name="awc-TaskCategory-radio" value="Commercial" />Commercial
<input type="radio" class="awc-TaskCategory" name="awc-TaskCategory-radio" value="Industrial" />Industrial
<input type="radio" class="awc-TaskCategory" name="awc-TaskCategory-radio" value="Other" />Other
</div>
<div class="acs-edit-view-2">
<span class="acs-span-medium">Category</span>
<input type="radio" class="awc-TaskCategory" name="awc-TaskCategory-radio" value="Scheduled" />Scheduled
I just added this logic last month - first time I had a set of RADIO buttons that had to come from the type of user. I could have done it with a bunch of hidden HTML but I wanted to make the field really be based on what TYPE's of tasks might be in the TASK TYPE table.
I'm a bit of a radical when it comes to business logic.
I believe it belongs in SPROCS in the database.
I do things like CALCULATE a payroll in SPROC - not small stuff - 1000+ employee towns.
The nice thing about calling a CALCULATE payroll in a SPROC is that you can do it with ROLLBACK.
And to add more sugar to that you can do it in EVERY single report SPROC.
Why is that so powerful?
Now you can run a deduction register, for example, and have it be run in PRE-CHECK mode - before the actual payroll for a week is committed.
Sure you could do that in code - hold a transaction open. That is not something I consider a viable option with SQL.
I do big things in SPROCS. Schedule high school students into classes - a SPROC call that can take 30 minutes.
Adjudicate medical claim payments - all in a SPROC.
So I am 100% business logic in SPROCS - which really works nicely with my web app concept of UI-in-DB.
I did the exact same thing in VB6 - UI-in-DB.
And that old VB6 app is still in use - here's a quick select off an Application Connection tracking table at one of my older clients.
Select Min(OpenServTime),Max(OpenServTime),Sum(1) From AppConnect_T Where AppTitle='AMC'
2003-01-13 12:45:07.937 2015-08-01 05:30:04.913 180710
180,000 runs in 12 years - 53 a day (small number of users - maybe 50?)
I had a report writer engine buried in that VB6 app which I just ported to .Net and loaded up into my web methods - now I've got 100's of reports from the last decade that now can get generated as PDF's in my web app - automatic preview that the browser gives me - very nice.
I am very busy so DRY is more then an development principle - it's a life style with me.
The platform sz has is similar to what we've built here... although, I think ours a little simpler, and we use XML files. The cool thing about it is that all you need is the basic platform, a SQL Server & database, and you can develop just about any kind of app. We've used the platform to build our main system that we sell to the clients, but we've also eaten our own dog food by using it in house for a configuration management tool, an on-line service tool, I think we should expand it to also run our in-house IT help desk but that's out of my sphere of influence, we use it to run our online documentation and self-help forums... it's pretty powerful and ingenious... and infuriating all at the same time.
Everything is built as componments using "spec" (specification) files, which are then loaded into the system... these components can then be used stand alone, or even as a block in another spec...
Example - using sz's example above - let's say I want to display a list of students, be able to add new students, and edit existing ones. What we would end up with are 4 spec files: The list, an add form, an edit form and the page that puts all of the components together.Correction - 5 spec files... I forgot I'll need a students table as well -- which yes, we also define through a spec file... when it is loaded, the loader will issue the CREATE TABLE (or alter table if I've changed the spec) automatically for me.
Keeping out the IP stuff... here's what I'd end up with:
I didn't include "ID" as a field as it is automatically added for me - in fact the schema will throw a fit if I do try to include it. There's also a number of other featres I didn't include such as indexing, fkeys, even linking this table as an extension of another table such that the IDs will match one-for-one if need be (this allows us to extend base tables with out actualy altering thsoe table in the chance that the platform or product people chenge them... it allows us to isolate our customizations/changes w/o fear of them being overwritten.)Code:<TableSpec
Id="{guid value}"
Name="STUDENTS"
Description="Table containing a list of students enrolled."
Developer="TG"
>
<Fields>
<Field Name="STUDENTID" Description="Friendly ID of the student" DataType="String" MaxLength="9" Required="true" />
<Field Name="FNAME" Description="Student's first name" DataType="String" MaxLength="50" />
<Field Name="LNAME" Description="Student's last name" DataType="String" MaxLength="100" Required = "true" />
... more fields as needed...
</Fields>
</TableSpec>
Once load that, I have a table, ready to go, fully audited and everything.
Now I need to add a student to the system.
That's where it starts... then we can define the next section which defines the UI FieldsCode:<AddFormSpec
Id="{guid value}"
Name="Student add form"
Description="Adds student records to the system."
Developer="TG"
>
<SPDataForm>
<SaveImplementation SProc="USP_STUDENT_SAVE">
<CreateProcedure>
<![CDATA[
create procedure dbo.USP_STUDENT_SAVE
(
@ID uniqueidentifier = null output,
@STUDENTID nvarchar(9),
@FNAME nvarchar(50) = null,
@LNAME nvarchar(100),
@USERID uniqueidentifier = null
)
If @ID is null
set @ID = get_id()
-- there's more stuff to do here, like checking the USERID (the person creating the record) and setting it if it is null (connection is used for that)
-- then insert into the table:
insert into dbo.STUDENTS (ID, STUDENTID, FNAME, LNAME, {more fields here as needed...}, CREATEDBY, CHANGEDBY, DATEADDED, DATECHANGED)
values (@ID, @STUDENTID, @FNAME, @LNAME, {more fields as needed...}, @USERID, @USERID, @CURRENTDATE, @CURRENTDATE) -- Current Date variable is part of that "other stuff"
return 0; -- success
]]>
</CreateProcedure>
</SaveImplementation>
</SPDataForm>
</AddFormSpec>
The platform then knows how to read that and transform it into an html document that can be fed to a client browser. It will automatically have save and cancel buttons. If I try to enter a student without an ID, it will let me know, same with the Last Name... it will also limit my entry to the appropriate number of characters.Code:<AddFormSpec
Id="{guid value}"
Name="Student add form"
Description="Adds student records to the system."
Developer="TG"
>
<SPDataForm>
<SaveImplementation SProc="USP_STUDENT_SAVE">
<CreateProcedure>
<![CDATA[
create procedure dbo.USP_STUDENT_SAVE
(
@ID uniqueidentifier = null output,
@STUDENTID nvarchar(9),
@FNAME nvarchar(50) = null,
@LNAME nvarchar(100),
@USERID uniqueidentifier = null
)
If @ID is null
set @ID = get_id()
-- there's more stuff to do here, like checking the USERID (the person creating the record) and setting it if it is null (connection is used for that)
-- then insert into the table:
insert into dbo.STUDENTS (ID, STUDENTID, FNAME, LNAME, {more fields here as needed...}, CREATEDBY, CHANGEDBY, DATEADDED, DATECHANGED)
values (@ID, @STUDENTID, @FNAME, @LNAME, {more fields as needed...}, @USERID, @USERID, @CURRENTDATE, @CURRENTDATE) -- Current Date variable is part of that "other stuff"
return 0; -- success
]]>
</CreateProcedure>
</SaveImplementation>
</SPDataForm>
<FormFields>
<FormField FieldID="FNAME" Caption="First name" DataType="string" MaxLength="50" />
<FormField FieldID="LNAME" Caption="Last name" DataType="string" MaxLength="100" Required="true" />
<FormField FieldID="STUDENTID" Caption="ID number" DataType="string" MaxLength="9" Required="true" />
</FormFields>
</AddFormSpec>
The list of FormFields also needs to match the parameters of the sproc - with some exceptions, like the ID and UserID which are auto parameters - or it will not load - I'll get an error and it will tell me that FormField X is not specified... vice versa too... if I accidentally include a FormField that is not a parameter, it will warn me.
-tg