Win a copy of Five Lines of Code this week in the OO, Patterns, UML and Refactoring forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Bear Bibeault
  • Ron McLeod
  • Jeanne Boyarsky
  • Paul Clapham
Sheriffs:
  • Tim Cooke
  • Liutauras Vilda
  • Junilu Lacar
Saloon Keepers:
  • Tim Moores
  • Stephan van Hulst
  • Tim Holloway
  • fred rosenberger
  • salvin francis
Bartenders:
  • Piet Souris
  • Frits Walraven
  • Carey Brown

Design pattern - Is this a good way to use the command pattern ?

 
Ranch Hand
Posts: 143
5
IntelliJ IDE Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have a java class SearchFilterWebPage which represents a web page for an online shopping website. All of its (filter) methods allow you to set a filter, just like you would on a real shopping website. For example enterPriceRange(min, max), selectBrandsFilter(String...brands), selectFreeShippingOnlyFilter(). The below code is very simple and only prints messages.

I made a function applyMultipleFilters(FilterCommand [] functions) which allows you to call any number of filter functions of SearchFilterWebPage class. Each filter function is represented by an instance of FilterCommand, i.e. the command pattern. For example, enterPriceRange(min, max) and selectBrandsFilter(String...brands) are represented by EnterPriceRangeFilter and SelectBrandsFilter classes respectively. Basically, I made a class to represent each filter function. Can someone please tell me if this is a good idea ? Is there any other way besides reflection and the command pattern to implement applyMultipleFilters(...) ?




Output :








 
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 2
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
First, it's good that you're looking for patterns to apply to a problem. Patterns can save you a lot of time and effort when applied appropriately. But that's also the key to leveraging them, applying them appropriately. I'm not so sure the Command pattern applies well to this context. The biggest "smell" that makes me think this is probably an anti-pattern (when a known good pattern is applied inappropriately) is that there's something off with the semantics created by thinking of filters as "commands."

The Command Pattern "is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time." Your problem's description doesn't really fit this definition (although I think I can see how you might think it does). However, needing to apply multiple criteria to data is a very common problem, so common in fact that the standard Java library provides classes and methods to facilitate writing solutions for this.

The Predicate functional interface defines a general API for testing whether a condition has been met. An object that implements the Predicate interface encapsulates the criteria to be met and the logic for checking objects against that criteria. It exposes the public boolean test(T something) method to check whether an object meets the encapsulated criteria/conditions.

Predicates can also be composed or chained together via the and(Predicate) and or(Predicate) default methods provided by the Predicate interface. I think you can even override these if you need custom behavior but you should be careful to preserve the semantics around their usage to avoid surprising others who might use your customizations.

And then you have the filter() method of a Stream which takes a Predicate (which can be composed or chained with other Predicates as I just described) as its argument. There are other Stream methods that take a predicate as well but the filter() method is the one that ties directly to your requirements.

Check out the java.util.function.Predicate API and the java.util.stream.Stream API documentation for more information.
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Tom Joe wrote:Is there any other way besides reflection


-- you probably want to stay away from reflection in general. In particular, reflection is probably the last thing that I would attempt to use to solve this type of problem, if and only if all other options have been exhausted.
 
Tom Joe
Ranch Hand
Posts: 143
5
IntelliJ IDE Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
@Junilu Lacar -

Let me clarify.

Suppose that there are many tests/functions which could use different combinations of filters. But, some of these tests/functions use the exact same set of filters. So, I thought of creating combinations of filters which could be reused in different tests/functions. There are two possible solutions to create such combinations that I can think of at the moment.

Solution 1 - Create a FilterChoices class which will provide all the filtering data to the  SearchFilterWebPage. FilterChoices can be a simple POJO with getters and setters. We could use the builder pattern to build this.
The minor problem is that applyMultipleFilters(...) function will need to do multiple checks to see which all filters have been set in FilterChoices and call the appropriate filter functions.

Solution 2 - Use the command pattern as mentioned in this post. Unfortunately, we end up creating a lot of code (even if simple) just to avoid checking for which filter functions to call.
 
Sheriff
Posts: 4870
317
IntelliJ IDE Python Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Tom Joe wrote:... reflection ...


If there was a :shudders: emoji I would use it here
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Tom Joe wrote:Let me clarify.


I think I'm pretty clear on what you're trying to do. However, you asked for opinions of whether it was a good approach and I gave mine, worth all of $0.02, that there are two problems I see with that approach:

1. It doesn't quite fit the context to which the Command Pattern is meant to be applied and
2. Because of this ill fit, there's cognitive dissonance created by the semantics of using names like "Filter" and then treating them as "commands". You may not suffer from this cognitive dissonance because it's your idea but others who have to understand where you're coming from will suffer from that dissonance. Maybe it's just me but I think other folks here will agree about the dissonance.
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Give me a few hours (it's Saturday and I have to cook and check off some things on my "Honey Do" list) to come up with a short example of how you can use Predicates, lambdas/closures, and higher-order functions to create a flexible and easily extensible filtering mechanism for a particular dataset.
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Ok, here's a small example (pardon all the static) that shows a Person value object, a PersonFilter static factory class, and some code that will apply multiple filters to a given set of data. You can define parameterized filters of people based on an age range or based on partial name match.

You can try running this live here: https://repl.it/@jlacar/HigherOrderFunctionsPredicates
(The code you find there may no longer be what's shown below; I tend to refactor often)

And here's the output of a sample run:

Menu:
1-add age filter
2-add name filter
3-show list
4-clear filters
0-exit
Choice: 3
All people in the list:
name=Bob Burgess, age=34
name=Cathy Burgess, age=29
name=Michael Johns, age=15
name=Liz Carter, age=12
name=Bob Jameson, age=47
name=Jim Stevens, age=24
name=Kim Johnson, age=27

Menu:
1-add age filter
2-add name filter
3-show list
4-clear filters
0-exit
Choice: 2
Enter name (or part of it): Burgess

Menu:
1-add age filter
2-add name filter
3-show list
4-clear filters
0-exit
Choice: 3
People who meet ANY of the criteria:
name=Bob Burgess, age=34
name=Cathy Burgess, age=29

Menu:
1-add age filter
2-add name filter
3-show list
4-clear filters
0-exit
Choice: 1
Enter age range (low high): 1 20

Menu:
1-add age filter
2-add name filter
3-show list
4-clear filters
0-exit
Choice: 3
People who meet ANY of the criteria:
name=Bob Burgess, age=34
name=Cathy Burgess, age=29
name=Michael Johns, age=15
name=Liz Carter, age=12

Menu:
1-add age filter
2-add name filter
3-show list
4-clear filters
0-exit
Choice: 0
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have since extended the example (see the code at the link I gave in the previous post) with additional functionality. It wasn't too hard to refactor and add.

Here's a sample run:

Menu:
1-add age filter
2-add name filter
3-show matches ANY criteria
4-show matches ALL criteria
5-clear filters
0-exit
Choice: 3
All people in the list:
name=Bob Burgess, age=34
name=Cathy Burgess, age=29
name=Michael Johns, age=15
name=Liz Carter, age=12
name=Bob Jameson, age=47
name=Jim Stevens, age=24
name=Kim Johnson, age=27


Menu:
1-add age filter
2-add name filter
3-show matches ANY criteria
4-show matches ALL criteria
5-clear filters
0-exit
Choice: 1
Enter age range (low high): 20 30
Filter by age added: Person.age in [20..30]

Menu:
1-add age filter
2-add name filter
3-show matches ANY criteria
4-show matches ALL criteria
5-clear filters
0-exit
Choice: 2
Enter name (or part of it): im
Filter by name added: Person.name contains im

Menu:
1-add age filter
2-add name filter
3-show matches ANY criteria
4-show matches ALL criteria
5-clear filters
0-exit
Choice: 3
People who meet ANY of the criteria:
name=Cathy Burgess, age=29
name=Jim Stevens, age=24
name=Kim Johnson, age=27


Menu:
1-add age filter
2-add name filter
3-show matches ANY criteria
4-show matches ALL criteria
5-clear filters
0-exit
Choice: 4
People who meet ALL of the criteria:
name=Jim Stevens, age=24
name=Kim Johnson, age=27


Menu:
1-add age filter
2-add name filter
3-show matches ANY criteria
4-show matches ALL criteria
5-clear filters
0-exit
Choice: 0
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I've also posted the code as a Gist, with a README that (hopefully) explains the key parts of the program.

See the Gist here: https://gist.github.com/jlacar/fb7daddfa4ce54d3d52f78cb5e12c83d
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Tom Joe wrote:Suppose that there are many tests/functions which could use different combinations of filters. But, some of these tests/functions use the exact same set of filters. So, I thought of creating combinations of filters which could be reused in different tests/functions. There are two possible solutions to create such combinations that I can think of at the moment.


The example I gave shows a different solution to that problem using Predicates, lambdas with closures, and an enum to encapsulate strategies. The filters are closed lambda expressions that capture user-specified parameters. They are put into a list whose elements can later be combined into a composed Predicate. You can even choose whether to combine the filters with ANY or ALL semantics.

There are a few smells in the design you've proposed that immediately jump out at me. I can go into details of what those smells are if you're interested.
 
Tom Joe
Ranch Hand
Posts: 143
5
IntelliJ IDE Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks for the detailed reply Junilu. I think I oversimplified my code example for brevity. I apologize for any confusion this created in understanding the problem. Here is a better representation of how it works. Meanwhile, I am trying to see if and how I could apply predicates to the below code.

Main method :



Output :



Supporting code :




 
Tom Joe
Ranch Hand
Posts: 143
5
IntelliJ IDE Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Here are the updates to my code. I think a "Procedure" makes more sense here instead of Function, Predicate or such. It is just like a Command with execute method. Maybe we should call it that instead of Procedure.

Procedure :



Main method :



Output :






NOTE - We'd like to have a "set" of filters to reuse in our code. The next step is to make that possible by moving the filter storage logic out of the Page class.
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Ok, I think I see what you're trying to do and yes, the semantics are closer to what I would think of as a Command rather than a Predicate/Filter. However, the approach you show is totally backwards from what I would do. To tie this in to the other thread you started about TDD, this would be a perfect example of how TDD and the kind of thinking it develops coulda/woulda led you to a much better solution. If you're up for it, I can show you how I would do it, TDD style.
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
First of all, if you really going to target Selenium, then learn about the Page Object pattern in Selenium. This will save you a lot of time and effort (and pain) if you start using this pattern in your tests.
 
Tom Joe
Ranch Hand
Posts: 143
5
IntelliJ IDE Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Updates again to previous code.

Main method :



Output :



Support :



Does it help if we replace Procedure with



and then replace mentions of Procedure in the main method with Procedure<SearchFilterWebPageAsJavaCode> ?
 
Tom Joe
Ranch Hand
Posts: 143
5
IntelliJ IDE Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:First of all, if you really going to target Selenium, then learn about the Page Object pattern in Selenium. This will save you a lot of time and effort (and pain) if you start using this pattern in your tests.



Yes, I am actually using Page objects in the actual code. I have not shown it in this code so that those who don't know Selenium can still understand the code and help. Plus, we don't get bogged down with selenium and the slow browser runs.
 
Tom Joe
Ranch Hand
Posts: 143
5
IntelliJ IDE Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Junilu Lacar wrote:
Ok, I think I see what you're trying to do and yes, the semantics are closer to what I would think of as a Command rather than a Predicate/Filter. However, the approach you show is totally backwards from what I would do. To tie this in to the other thread you started about TDD, this would be a perfect example of how TDD and the kind of thinking it develops coulda/woulda led you to a much better solution. If you're up for it, I can show you how I would do it, TDD style.



Thanks, but can we do it next week when you have time ? Right now, I have some stuff coming up which will take most of my time. Actually, I should not even be working on this post now, but I could not resist.

I sort of used TDD here. I wrote what the interface should look like and built the code accordingly. I wonder if it would be better to pick a new problem instead of this one for TDD, since I have a (ok ?) solution and hindsight might mess the TDD approach.
 
Tom Joe
Ranch Hand
Posts: 143
5
IntelliJ IDE Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Updates again :



>>> Updates in other code :

SearchFilterWebPageAsJavaCode :



MainMethod :


 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Tom Joe wrote:I sort of used TDD here. I wrote what the interface should look like and built the code accordingly. I wonder if it would be better to pick a new problem instead of this one for TDD, since I have a (ok ?) solution and hindsight might mess the TDD approach.


On the contrary, I think having done this in a different way before, it would give you a great way to compare how you'd approach this problem differently with TDD. Besides, you posited in the other thread you started about TDD that it's a bad idea and pined for "realistic" examples. I don't think you can argue that something you're actually working on the real world is not realistic enough for you.
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I've started a new thread here: https://coderanch.com/t/729600/engineering/TDD-Exercise-Search-page-filters

Whenever you're ready to begin, just post a reply there.
 
Junilu Lacar
Sheriff
Posts: 15756
264
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Tom Joe wrote:I sort of used TDD here. I wrote what the interface should look like and built the code accordingly.


My first question then would be that if you "sort of used TDD," where are the tests you wrote before you wrote the interface? If you didn't write any test before writing anything else, then "sort of" doesn't even come close.
 
That is a really big piece of pie for such a tiny ad:
Thread Boost feature
https://coderanch.com/t/674455/Thread-Boost-feature
    Bookmark Topic Watch Topic
  • New Topic