In JavaFX 8 can I provide a stylesheet from a String? - css

Is it possible to wrap a whole Stylesheet in a string and apply it to a certain node?
Usage case would be to add specific (non changeble) behavior for PseudoClass.
I know I can use pane.getStylesheets().add(getClass().getResource("mycss.css").toExternalForm());, but I would like to know if there's some way to embrd it direcly in source; something along the lines:
pane.getStylesheets().add(
".button:ok { -fx-background-color: green; }\n"+
".button:ko { -fx-background-color: red; }");

I found a way of doing this by defining a new URL connection:
private String css;
public void initialize() {
...
// to be done only once.
URL.setURLStreamHandlerFactory(new StringURLStreamHandlerFactory());
...
}
private void updateCss(Node node) {
// can be done multiple times.
css = createCSS();
node.getStylesheets().setAll("internal:"+System.nanoTime()+"stylesheet.css");
}
private class StringURLConnection extends URLConnection {
public StringURLConnection(URL url){
super(url);
}
#Override public void connect() throws IOException {}
#Override public InputStream getInputStream() throws IOException {
return new StringBufferInputStream(css);
}
}
private class StringURLStreamHandlerFactory implements URLStreamHandlerFactory {
URLStreamHandler streamHandler = new URLStreamHandler(){
#Override protected URLConnection openConnection(URL url) throws IOException {
if (url.toString().toLowerCase().endsWith(".css")) {
return new StringURLConnection(url);
}
throw new FileNotFoundException();
}
};
#Override public URLStreamHandler createURLStreamHandler(String protocol) {
if ("internal".equals(protocol)) {
return streamHandler;
}
return null;
}
}
Obviously protocol "internal" can be any (non clashing) well-formed string and (in this simple example) filepath is compeltely ignored.
I use this to set the global .css, so I do not need to remember multiple strings.
It seems the Stream is opened just once, but I do not know if this holds true in all cases.
Feel free to complicate the code as needed ;)
Credit for this method goes to Jasper Potts (see this example)

Here is my CSS updater class based on ZioBytre's answer (+1 works very well).
This is a self contained class that can easily be copied to a project and used as it is.
It has a dependency on the commons IO IOUtils class to return a Stream based on a String. But this could easily be inlined or replaced by another library if needed.
I use this class in a project where the CSS is dynamically editable inside the application, on the server side, and pushed to the JavaFX clients. It could be used in any scenario where the CSS string does not come from a file or URL but from another source (server app, database, user input...)
It has a method to bind a string property so that the CSS changes will be automatically applied as soon as they happen.
/**
* Class that handles the update of the CSS on the scene or any parent.
*
* Since in JavaFX, stylesheets can only be loaded from files or URLs, it implements a handler to create a magic "internal:stylesheet.css" url for our css string
* see : https://github.com/fxexperience/code/blob/master/FXExperienceTools/src/com/fxexperience/tools/caspianstyler/CaspianStylerMainFrame.java
* and : http://stackoverflow.com/questions/24704515/in-javafx-8-can-i-provide-a-stylesheet-from-a-string
*/
public class FXCSSUpdater {
// URL Handler to create magic "internal:stylesheet.css" url for our css string
{
URL.setURLStreamHandlerFactory(new StringURLStreamHandlerFactory());
}
private String css;
private Scene scene;
public FXCSSUpdater(Scene scene) {
this.scene = scene;
}
public void bindCss(StringProperty cssProperty){
cssProperty.addListener(e -> {
this.css = cssProperty.get();
Platform.runLater(()->{
scene.getStylesheets().clear();
scene.getStylesheets().add("internal:stylesheet.css");
});
});
}
public void applyCssToParent(Parent parent){
parent.getStylesheets().clear();
scene.getStylesheets().add("internal:stylesheet.css");
}
/**
* URLConnection implementation that returns the css string property, as a stream, in the getInputStream method.
*/
private class StringURLConnection extends URLConnection {
public StringURLConnection(URL url){
super(url);
}
#Override
public void connect() throws IOException {}
#Override public InputStream getInputStream() throws IOException {
return IOUtils.toInputStream(css);
}
}
/**
* URL Handler to create magic "internal:stylesheet.css" url for our css string
*/
private class StringURLStreamHandlerFactory implements URLStreamHandlerFactory {
URLStreamHandler streamHandler = new URLStreamHandler(){
#Override
protected URLConnection openConnection(URL url) throws IOException {
if (url.toString().toLowerCase().endsWith(".css")) {
return new StringURLConnection(url);
}
throw new FileNotFoundException();
}
};
#Override
public URLStreamHandler createURLStreamHandler(String protocol) {
if ("internal".equals(protocol)) {
return streamHandler;
}
return null;
}
}
}
Usage :
StringProperty cssProp = new SimpleStringProperty(".root {-fx-background-color : red}");
FXCSSUpdater updater = new FXCSSUpdater(scene);
updater.bindCss(cssProp);
//new style will be applied to the scene automatically
cssProp.set(".root {-fx-background-color : green}");
//manually apply css to another node
cssUpdater.applyCssToParent(((Parent)popover.getSkin().getNode()));

For anyone who is writing framework level code that does not want to use up the one and only override of the global, static url stream factory, you can instead tie into the internal "service loader" framework in the URL class itself.
To do this, you must create a class named Handler extends URLStreamHandler and update the system property java.protocol.handler.pkgs to point to the package of that class, minus the final package suffix. So, com.fu.css would set the property to com.fu, then all css:my/path requests would route to this handler.
I will paste the class I am using below; forgive the weird collections and supplier interfaces; you can guess what these do and replace them with standard utilities without much trouble.
package xapi.jre.ui.css;
import xapi.collect.X_Collect;
import xapi.collect.api.CollectionOptions;
import xapi.collect.api.StringTo;
import xapi.fu.Out1;
import xapi.io.X_IO;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.charset.Charset;
/**
* I abhor the name of this class,
* but it must be called "Handler" in order for java.net.URL to be able to find us.
*
* It sucks, but it's not our api, and it's the only way to get dynamic stylesheets in JavaFx,
* short of overriding the url stream handler directly (and this can only be done once in a single
* JVM, and as framework-level code, it is unacceptable to prevent clients from choosing to
* override the stream handler themselves).
*
* Created by James X. Nelson (james #wetheinter.net) on 8/21/16.
*/
public class Handler extends URLStreamHandler {
private static final StringTo<Out1<String>> dynamicFiles;
static {
// Ensure that we are registered as a url protocol handler for css:/path css files.
String was = System.getProperty("java.protocol.handler.pkgs", "");
System.setProperty("java.protocol.handler.pkgs", Handler.class.getPackage().getName().replace(".css", "") +
(was.isEmpty() ? "" : "|" + was ));
dynamicFiles = X_Collect.newStringMap(Out1.class,
CollectionOptions.asConcurrent(true)
.mutable(true)
.insertionOrdered(false)
.build());
}
public static void registerStyleSheet(String path, Out1<String> contents) {
dynamicFiles.put(path, contents);
}
#Override
protected URLConnection openConnection(URL u) throws IOException {
final String path = u.getPath();
final Out1<String> file = dynamicFiles.get(path);
return new StringURLConnection(u, file);
}
private static class StringURLConnection extends URLConnection {
private final Out1<String> contents;
public StringURLConnection(URL url, Out1<String> contents){
super(url);
this.contents = contents;
}
#Override
public void connect() throws IOException {}
#Override public InputStream getInputStream() throws IOException {
return X_IO.toStream(contents.out1(), Charset.defaultCharset().name());
}
}
}
Now, any code can call Handler.registerStylesheet("my/path", ()->"* { -fx-css: blah }");, and you can use this stylesheet anywhere via "css:my/path".
Note that I am only looking at the path portion of the url; I intend to leverage query parameters to further increase the dynamism (by using a css factory that accepts a map of parameters), but that is beyond the scope of this question.

I looked at the documentation and I don’t see a built-in way to do that. getStylesheets is the only stylesheet-related method in Parent, and it only accepts “string URLs linking to the stylesheets”, not stylesheets themselves. It returns a generic ObservableList, so its return value has no special methods for different types; only a generic add. This is consistent with getResource returning a URL, and toExternalForm() merely returning a String version of that URL object.
However, there is one thing you could try: a data URI. Instead of passing in a generated URI to a stylesheet file, pass in a data URI whose contents are that stylesheet. I don’t know if the API would accept that kind of URI, though, given that the CSS Reference Guide linked in getStylesheets’s documentation says
A style sheet URL may be an absolute URL or a relative URL.
Try a really simple data URI first to see if it works. You can generate one using this online tool. If Java does accept a data URI, then you just need to wrap your CSS-containing String with some method call that converts a String to a data URI, something like this:
pane.getStylesheets().add(new DataURI(
".button:ok { -fx-background-color: green; }\n"+
".button:ko { -fx-background-color: red; }").toString());
The class DataURI is hypothetical. If JavaFX accepts a manually-generated data URI, then you will have to find a library that provides that DataURI class yourself; I’m sure one exists somewhere.
There is also a way to specify inline CSS for a certain Node as a String, which is almost what you are looking for. It is mentioned in the CSS Reference Guide:
CSS styles can come from style sheets or inline styles. Style sheets are loaded from the URLs specified in the stylesheets variable of the Scene object. If the scene graph contains a Control, a default user agent style sheet is loaded. Inline styles are specified via the Node setStyle API. Inline styles are analogous to the style="…" attribute of an HTML element.
However, it sounds like it does not support selectors in the CSS, only rules – so rather than saying .red { color: red; }, you would only be able to write color: red;, and it would apply to all children of that Node. This doesn’t sound like what you want. So a data URI is your only hope.
EDIT: While this is a smart idea (I didn't know about data URIs before) it doesn't work. I have the same requirement so I tried. It doesn't raise an exception but there is a warning in the the logs and the styles are not applied :
I used this style :
.root{
-fx-font-family: "Muli";
-fx-font-weight: lighter;
-fx-font-size: 35pt;
-fx-padding: 0;
-fx-spacing: 0;
}
And using the provided tool generated the following data URI :
data:text/css;charset=utf-8,.root%7B%0D%0A%20%20%20%20-fx-font-family%3A%20%22Muli%22%3B%0D%0A%20%20%20%20-fx-font-weight%3A%20lighter%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-padding%3A%200%3B%0D%0A%20%20%20%20-fx-spacing%3A%200%3B%0D%0A%7D
Applying it to my scene :
scene.getStylesheets().add("data:text/css;charset=utf-8,.root%7B%0D%0A%20%20%20%20-fx-font-family%3A%20%22Muli%22%3B%0D%0A%20%20%20%20-fx-font-weight%3A%20lighter%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-padding%3A%200%3B%0D%0A%20%20%20%20-fx-spacing%3A%200%3B%0D%0A%7D");
Results in (pardon my French, AVERTISSEMENT=WARNING):
janv. 07, 2015 12:02:03 PM com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged
AVERTISSEMENT: Resource "data:text/css;charset=utf-8,%23header%7B%0D%0A%20%20%20%20-fx-background-color%3A%23002D27%3B%0D%0A%20%20%20%20-fx-font-size%3A%2035pt%3B%0D%0A%20%20%20%20-fx-text-fill%3A%20%23fff%3B%0D%0A%7D" not found.
So sadly JavaFX seems not to be aware of data URIs.

Since JavaFX 17 it is now possible to use data URIs.
For example,
scene.getStylesheets().add("data:text/css;base64," + Base64.getEncoder().encodeToString("* { -fx-color: red; }".getBytes(StandardCharsets.UTF_8)));
will simply work in JavaFX 17.

Related

JavaFX Implementing 2 different MapChangeListeners [duplicate]

This question already has answers here:
How to make a Java class that implements one interface with two generic types?
(9 answers)
Closed 8 years ago.
I have the following interface, which I want to implement multiple times in my classes:
public interface EventListener<T extends Event>
{
public void onEvent(T event);
}
Now, I want to be able to implement this interface in the following way:
class Foo implements EventListener<LoginEvent>, EventListener<LogoutEvent>
{
#Override
public void onEvent(LoginEvent event)
{
}
#Override
public void onEvent(LogoutEvent event)
{
}
}
However, this gives me the error: Duplicate class com.foo.EventListener on the line:
class Foo implements EventListener<LoginEvent>, EventListener<LogoutEvent>
Is it possible to implement the interface twice with different generics? If not, what's the next closest thing I can do to achieve what I'm trying to do here?
Is it possible to implement the interface twice with different generics
Unfortunately no. The reason you can't implement the same interface twice is because of type erasure. The compiler will handle type parameters, and a runtime EventListener<X> is just a EventListener
If not, what's the next closest thing I can do to achieve what I'm trying to do here?
Type erasure can work in our favor. Once you know that EventListener<X> and EventListener<Y> are just raw EventListener at run-time, it is easier than you think to write an EventListener that can deal with different kinds of Events. Bellow is a solution that passes the IS-A test for EventListener and correctly handles both Login and Logout events by means of simple delegation:
#SuppressWarnings("rawtypes")
public class Foo implements EventListener {
// Map delegation, but could be anything really
private final Map<Class<? extends Event>, EventListener> listeners;
// Concrete Listener for Login - could be anonymous
private class LoginListener implements EventListener<LoginEvent> {
public void onEvent(LoginEvent event) {
System.out.println("Login");
}
}
// Concrete Listener for Logout - could be anonymous
private class LogoutListener implements EventListener<LogoutEvent> {
public void onEvent(LogoutEvent event) {
System.out.println("Logout");
}
}
public Foo() {
#SuppressWarnings("rawtypes")
Map<Class<? extends Event>, EventListener> temp = new HashMap<>();
// LoginEvents will be routed to LoginListener
temp.put(LoginEvent.class, new LoginListener());
// LogoutEvents will be routed to LoginListener
temp.put(LogoutEvent.class, new LogoutListener());
listeners = Collections.unmodifiableMap(temp);
}
#SuppressWarnings("unchecked")
#Override
public void onEvent(Event event) {
// Maps make it easy to delegate, but again, this could be anything
if (listeners.containsKey(event.getClass())) {
listeners.get(event.getClass()).onEvent(event);
} else {
/* Screams if a unsupported event gets passed
* Comment this line if you want to ignore
* unsupported events
*/
throw new IllegalArgumentException("Event not supported");
}
}
public static void main(String[] args) {
Foo foo = new Foo();
System.out.println(foo instanceof EventListener); // true
foo.onEvent(new LoginEvent()); // Login
foo.onEvent(new LogoutEvent()); // Logout
}
}
The suppress warnings are there because we are "abusing" type erasure and delegating to two different event listeners based on the event concrete type. I have chosen to do it using a HashMap and the run-time Event class, but there are a lot of other possible implementations. You could use anonymous inner classes like #user949300 suggested, you could include a getEventType discriminator on the Event class to know what do to with each event and so on.
By using this code for all effects you are creating a single EventListener able to handle two kinds of events. The workaround is 100% self-contained (no need to expose the internal EventListeners).
Finally, there is one last issue that may bother you. At compile time Foo type is actually EventListener. Now, API methods out of your control may be expecting parametrized EventListeners:
public void addLoginListener(EventListener<LoginEvent> event) { // ...
// OR
public void addLogoutListener(EventListener<LogoutEvent> event) { // ...
Again, at run-time both of those methods deal with raw EventListeners. So by having Foo implement a raw interface the compiler will be happy to let you get away with just a type safety warning (which you can disregard with #SuppressWarnings("unchecked")):
eventSource.addLoginListener(foo); // works
While all of this may seem daunting, just repeat to yourself "The compiler is trying to trick me (or save me); there is no spoon <T>. Once you scratch your head for a couple of months trying to make legacy code written before Java 1.5 work with modern code full of type parameters, type erasure becomes second nature to you.
You need to use inner or anonymous classes. For instance:
class Foo {
public EventListener<X> asXListener() {
return new EventListener<X>() {
// code here can refer to Foo
};
}
public EventListener<Y> asYListener() {
return new EventListener<Y>() {
// code here can refer to Foo
};
}
}
This is not possible.
But for that you could create two different classes that implement EventListener interface with two different arguments.
public class Login implements EventListener<LoginEvent> {
public void onEvent(LoginEvent event) {
// TODO Auto-generated method stub
}
}
public class Logout implements EventListener<LogoutEvent> {
public void onEvent(LogoutEvent event) {
// TODO Auto-generated method stub
}
}

Minecraft modding block constructer error

I'm making a mod, and I am getting an error(no duh) and I have tried searching it up but I want an answer specific to my problem because I am not very good at this. I am getting this error in my block class.
Implicit super constructor Block() is undefined for default constructor. Must define an explicit constructor
and I don't know how to fix it. Please Help its for a project.
block class:
package GDMCrocknrollkid.fandomcraft;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
public class BlockCbBlock extends Block {
protected BlockCbBlock(Material material) {
super(material);
}
}
mod class:
package GDMCrocknrollkid.fandomcraft;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;
#Mod(modid = "fc", name = "Fandomcraft", version = "1.0")
public class fandomcraft {
public static Item itemCbIngot;
public static Block blockCbBlock;
#EventHandler
public void preInit(FMLPreInitializationEvent event){
//Item/Block initialization and registering
//Config Handling
itemCbIngot = new ItemCbIngot().setUnlocalizedName("ItemCbIngot").setTextureName("fc:itemCbIngot"); //item.itemCbIngot.name
blockCbBlock = new BlockCbBlock(Material.iron);
GameRegistry.registerItem(itemCbIngot, itemCbIngot.getUnlocalizedName().substring(5));
}
#EventHandler
public void init(FMLInitializationEvent event){
//Proxy, TileEntity, entity, GUI and Packet Registering
}
#EventHandler
public void postInit(FMLPostInitializationEvent event) {
}
}
This error pertains to all of java, not just minecraft forge. Check this for some more reference. There are a couple possible reasons for this error. It is most likely 1, but 2 and 3 can be a contributing factor to the error.
Your BlockCbBlock Class declares a constructor that is not the default, no-argument constructor that the compiler would otherwise provide (that is, if the Block class doesn't have a constructor) and, if in fact the Block class is using the default constructor, then you can't call super() on the arguements because the Block class uses a constructor with no arguments. Because of this, if you wanted to modify the Block constructor, it would be safier and easier to create a custom construcotr inside of the BlockCbBlock class itself.
You are trying to inherit the constructor of Block, but you have declared it as protected, when the constructor in your class should be public to match the inherited .
If you're using Eclipse, it can give this error when you have your project setup incorrectly (system configuration mismatch)
Probably not directly realted to this specific error, but a possible cause of other errors in the near future; you are using the annotation #EventHandler, but you have not actually declared the forge event handler.
You don't actually register the block for some reason. Even if you're using the block as a recipe item, you still need to register it
To fix potential problems 1, 2, and 4, try this (obtained from here):
package GDMCrocknrollkid.fandomcraft;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
private final String name = "BlockCbBlock";
public class BlockCbBlock extends Block {
public BlockCbBlock() {
super(Material.iron);
GameRegistry.registerBlock(this, name);
setUnlocalizedName(Reference.MODID + "_" + name);
setCreativeTab(CreativeTabs.tabBlock);
}
public String getName() {
return name;
}
}
This way, you'll declare its UnlocalizedName, Material, and CreativeTab ahead of time. This method might be unnecessary, but its a good precaution to help prevent the error. Now, all you have to do is declare it like this:
//You need to make your own EventHandler class. Search online for that.
FCEventHandler handler = new FCEventHandler();
#EventHandler
public void preInit(FMLPreInitializationEvent event){
//Config Handling
//event handler registry
FMLCommonHandler.instance().bus().register(handler);
MinecraftForge.EVENT_BUS.register(handler);
//the same thing can be similarly done with this if you wish
itemCbIngot = new ItemCbIngot().setUnlocalizedName("ItemCbIngot").setTextureName("fc:itemCbIngot");
blockCbBlock = new BlockCbBlock();
GameRegistry.registerItem(itemCbIngot, itemCbIngot.getUnlocalizedName().substring(5));
}

Umbraco 7: Change URLs to uppercase / lowercase

How can I change all URLs to uppercase / lowercase, or change the default naming convention?
Eg. from:
http://our.umbraco.org/projects/backoffice-extensions/
to:
http://our.umbraco.org/Projects/Backoffice-Extensions/
This is not so hard if you know how to program C#.
You basically need to write your own UrlSegmentProvider (see documentation).
public class UppercaseUrlSegmentProvider: IUrlSegmentProvider
{
private readonly IUrlSegmentProvider provider = new DefaultUrlSegmentProvider();
public string GetUrlSegment(IContentBase content)
{
return this.GetUrlSegment(content, CultureInfo.CurrentCulture);
}
public string GetUrlSegment(IContentBase content, CultureInfo culture)
{
// Maybe you don't want to do that for all contentTypes
// if so, check on the contentType: if (content.ContentTypeId != 1086)
var segment = this.provider.GetUrlSegment(content);
// for the sake of simplicity I have put everything in uppercase,
// you could of course implement something like this:
// http://www.java2s.com/Code/CSharp/Data-Types/CamelCase.htm
return segment.ToUpper().ToUrlSegment();
}
}
To activate your segment provider, you can use the ApplicationStarting method of the ApplicationEventHandler.
public class MySegmentEvents : ApplicationEventHandler
{
protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
base.ApplicationStarting(umbracoApplication, applicationContext);
// UrlSegmentProviderResolver.Current.Clear();
UrlSegmentProviderResolver.Current.InsertType<UppercaseUrlSegmentProvider>(0);
}
}
Attention, if you have implemented the code above, the existing nodes won't change automatically. It's only after a "Save And Publish" that your URL of the particular node will have it's new "segment".

Create path from path value?

Can i create a Path using its value??
Path p=new Path();
p.getElements().add(new MoveTo(mouse.getX(), mouse.getY()));
System.out.Print(p);
This will print
Path#29f12030
can i convert this into path again?
You have already created a path and it's still a path, you don't need to convert it.
When you call System.out.print(p) you invoke the default toString function on your p object, which prints an internal Java reference to your Path (e.g. Path#29f12030).
If you override the default toString method with your own implementation, as is shown in the sample below, your print statement will display the value of the path.
public class PrintPath extends Application {
public static void main(String[] args) throws Exception { launch(args); }
#Override public void start(final Stage stage) throws Exception {
Path p = new PrintedPath();
p.getElements().add(new MoveTo(100, 150));
System.out.println(p);
stage.setScene(new Scene(new StackPane()));
stage.show();
}
class PrintedPath extends Path {
#Override public String toString() {
StringBuilder b = new StringBuilder();
for (PathElement e: getElements()) {
if (e instanceof MoveTo) {
MoveTo m = (MoveTo) e;
b.append("M").append(m.getX()).append(" ").append(m.getY()).append(" ");
}
// logic to display other path element types could be added here . . .
}
return "Path{ " + b.toString() + "}";
}
}
}
I think you should elaborate your purpose of sending data over network in a context of your app architecture. Give some fundamental details about it. In my understanding, you want to send a Path instance over the network and able to process it on the other end. If so,
- have a look to a serialization API. Read the post about it "How to transfer objects over network using java". Extend the Path or wrap it into another class then implement Serializable.
- Or, refer to Java Architecture for XML Binding (JAXB). Basically by using it you can convert/marshal the objects to XML strings and transfer over the network and then unmarshal it. Here is hello world example.
- Or, implement your own encoding/decoding mechanism to transfer the Path object.

Design Pattern Advice: Rule checker

I sell products throgh my website. Recently we've been given a list of rules that need to be checked against each order, to make sure it's not fraudulent. So this list of rules/fraud indicators will change and grow so I want to make sure it's easily maintainable and really solid.
I'm thinking I have an abstract class rule that each of the rules implements.
abstract class Rule
{
public string Message;
public bool Success;
public void CheckOrder(OrderItem currentOrder);
}
class FakeCreditCardNumberRule : Rule
{
public string Message = "Fake CC Number Rule";
public void CheckOrder(OrderItem currentOrder)
{
currentOrder.CreditCardNumber = "1234-5678-9012-3456";
Success = false;
}
}
class ReallyLargeOrderRule : Rule
{
public string Message = "Really Large Order Rule";
public void CheckOrder(OrderItem currentOrder)
{
currentOrder.ItemsOrder.Count > 100;
Success = false;
}
}
Then I'm thinking of having a class that accepts an Order object in it's costructor and checks though the list of rules. Something like:
class FraudChecker
{
List<Rule> rules;
public FraudChecker(OrderItem currentOrder)
{
foreach(var rule in rules)
{
rule.CheckOrder(currentOrder);
}
}
}
So I was trying to think of the best place/best way to populate the FraudChecker
.Rules list and started thinking there might be some nice design pattern that does something like what I'm doing.
Has anyone seen a design pattern I should use here? Or can anyone think of a good place to populate this list?
-Evan
Specification Pattern
I've been dealing with a very similar issue.
I've found the Specification Pattern to be particularly useful.
For me the main benefits of the pattern is the way it incorporates chaining.
The link above provides a basic overview, and the related links in the article are useful too. After, if you do some more searching you'll find more detailed examples.
You have already used the Startegy pattern... I believe a factory pattern can solve your problem.
Link
I would probably go with my own implementation of enum. I would create a class like this:
public sealed class Rules
{
public static readonly Rule FakeCreditCardNumberRule = new FakeCreditCardNumberRule ();
public static readonly Rule ReallyLargeOrderRule = new ReallyLargeOrderRule ();
//here I would write more
private static readonly List<Rule> _all = List<Rule> { FakeCreditCardNumberRule, ReallyLargeOrderRule };
//every time you add new rule to the class you have to add it to the _all list also
public static IEnumerable<Rule> All
{
get
{
return _all;
}
}
public static FraudChecker(OrderItem currentOrder)
{
foreach(var rule in All)
{
rule.CheckOrder(currentOrder);
}
}
}
and then you can use it like this:
Rules.FraudChecker(currentOrder);
or
Rules.FakeCreditCardNumberRule.CheckOrder(currentOrder);
You can add every new rule to the Rules class. You can also create new methods in it that will process only part of the Rules and so on.

Resources