Can one Object "Listen" to another Object's "State"

  • (2 Pages)
  • +
  • 1
  • 2

19 Replies - 1125 Views - Last Post: 01 February 2013 - 10:20 AM Rate Topic: -----

#16 baavgai  Icon User is offline

  • Dreaming Coder
  • member icon

Reputation: 5874
  • View blog
  • Posts: 12,754
  • Joined: 16-October 07

Re: Can one Object "Listen" to another Object's "State"

Posted 30 January 2013 - 11:02 AM

Yep. The idea is to make each element responsible for only as much as it needs to be.

In OOP programming beginners often wonder how objects on a form talk to their owners. You could give every object a reference to it's owner, but that gets real messy real quick. Instead, you give an object the ability to raise a flag to anyone who wants to listen. In this way, the controls on a form can notify the form of changes without needing to know anything about the form. "Loosly coupled" is your friend.

To avoid complexity, I used one of the mechanisms already in place, the ActionListener. This is a kind of observer pattern, but you rely on the observable elements to support a method of registration, in this case the standard addActionListener.

You could go nuts and have all the elements of the form listen to all the other elements of the form. It's more chaotic, but if the mechanism was in place, it might be simple...

Ok, riffing off of CasiOo's idea, here's a bunch of object that are chat happy.
import javax.swing.*;
import java.util.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;


interface GenericObserver<T, E> { 
	public void update(T sender, E data);
}

interface GenericObservable<T, E> {
	public void addObserver(GenericObserver<T, E> observer);
	public void removeObserver(GenericObserver<T, E> observer);
}

interface LightPosition {
	int getRow();
	int getCol();
}

interface LightState {
	boolean isOn();
}

class Light extends JButton
	implements GenericObservable<LightPosition, LightState>,
		GenericObserver<LightPosition, LightState>
{
	private final int row, col;
	private final java.util.List<GenericObserver<LightPosition, LightState>> listeners;
	private boolean lit;
	public Light(int row, int col) { 
		this.listeners = new java.util.ArrayList<GenericObserver<LightPosition, LightState>>();
		this.row=row; 
		this.col=col; 
		this.setLight(false);
		this.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				toggleState();
				LightPosition pos = getPosition();
				LightState state = getState();
				for(GenericObserver<LightPosition, LightState> item : listeners) {
					item.update(pos, state);
				}
			}
		});
	}
	
	public LightState getState() { 
		return new LightState() { public boolean isOn() { return lit; } };
	}
	
	public LightPosition getPosition() { 
		return new LightPosition() {
			public int getRow() { return row; }
			public int getCol() { return col; }
		};
	}

	public void setLight(boolean value) {
		this.lit = value;
		this.setBackground(this.lit ? Color.YELLOW : Color.BLACK);
	}
	
	public void toggleState() { setLight(!this.lit); }

	private boolean isNeightbor(LightPosition pos) {
		if (pos.getCol()==this.col-1 && pos.getRow()==this.row) { return true; }
		if (pos.getCol()==this.col+1 && pos.getRow()==this.row) { return true; }
		if (pos.getCol()==this.col && pos.getRow()==this.row-1) { return true; }
		if (pos.getCol()==this.col && pos.getRow()==this.row+1) { return true; }
		return false;
	}
	
	public void update(LightPosition pos, LightState state) {
		if (isNeightbor(pos)) { this.toggleState(); }
	}
	
	public void addObserver(GenericObserver<LightPosition, LightState> observer) {
		this.listeners.add(observer);
	}

	public void removeObserver(GenericObserver<LightPosition, LightState> observer) {
		this.listeners.remove(observer);
	}
}


public class Lights extends JFrame {
	private final int ROWS = 5, COLS = 5;
	private final Light [] cells;

	public Lights() {
		cells = new Light[ROWS * COLS];
		setLayout(new GridLayout(ROWS,COLS,0,0));
		setBackground(Color.green);
		int count = 0;
		for(int row=0; row<ROWS; row++) {
			for(int col=0; col<COLS; col++) {
				Light b = new Light(row, col);
				this.cells[count++] = b;
				this.add(B)/>;
			}
		}
		// wire them up
		for(int i=0; i<count; i++) {
			Light cell = this.cells[i];
			for(int j=0; j<count; j++) {
				if (i!=j) {
					cell.addObserver(this.cells[j]);
				}
			}
		}
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(500,500);
	}
	
	private void setLit(int row, int col, boolean value) {
		this.cells[row * COLS + col].setLight(value);
	}
}



Note, the main form actually doesn't get involved with handling the clicks. The buttons just talk to each other, even though they don't know who they are talking to.
Was This Post Helpful? 0
  • +
  • -

#17 grimpirate  Icon User is offline

  • Pirate King
  • member icon

Reputation: 149
  • View blog
  • Posts: 714
  • Joined: 03-August 06

Re: Can one Object "Listen" to another Object's "State"

Posted 31 January 2013 - 11:57 AM

Thank you for all the help gents. Unfortunately, while the concept of observable/observer does make conceptual sense to me, I find I have difficulty in its implementation. This is not to say I was unable to understand your code sample baavgai. It's just that I understood it mechanically, as opposed to intuitively. Therefore, if I did something similar I would simply be imitating your code as opposed to really understanding what my code is doing. Which is unfortunate because your solution is very elegant.

How I finally worked the problem was to do precisely what, to me, seemed like a very oafish way of tackling the problem. I created a GameBoard class that is essentially a two-dimensional array of JButtons. I made it into a public static field in the driver class of the program such that it provides the overall picture of the board to each button. When a button is clicked it checks its neighbors' states by using the getButton method (to retrieve said buttons) of the GameBoard class and then proceeds to get/set their states accordingly.

This seemed to be emulating the observable/observer relationship in the sense that the board observes what each button does and responds accordingly, but again, I'm missing something conceptually to further my understanding. In any case, it works, so I've solved my immediate problem and at the very least I am no longer ignorant of the existence of observer/observable.

For your viewing pleasure:
http://www.youtube.c...h?v=4KrJbdb4iWQ
Was This Post Helpful? 0
  • +
  • -

#18 baavgai  Icon User is offline

  • Dreaming Coder
  • member icon

Reputation: 5874
  • View blog
  • Posts: 12,754
  • Joined: 16-October 07

Re: Can one Object "Listen" to another Object's "State"

Posted 31 January 2013 - 01:52 PM

Repeat after me: tight coupling bad, static bad, globals bad! ( There are exceptions to every case. )

What you can do, that may make sense to you, is just have a talk back interface. You don't need static, you just need the child to have some access to a parent method. But you don't need all the parent methods, just the ones that make sense. Itemize what the child is calling in an interface. Indeed, interfaces good! ;)

Yet another riff on my little lights game:
import javax.swing.*;
import java.util.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;

interface Light {
	int getRow();
	int getCol();
	boolean isOn();
	void toggleState();
	void setLight(boolean on);
}

interface World {
	java.util.List<Light> getNeighbors(Light light);
}

class LightButton extends JButton implements Light {
	private final int row, col;
	private final World world;
	private boolean lit;
	
	public LightButton(int row, int col, World world) { 
		this.row=row; 
		this.col=col;
		this.world = world;
		this.setLight(false);
		this.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) { toggleNeighbors();}
		});
	}
	
	public int getRow() { return row; }
	public int getCol() { return col; }
	public boolean isOn() { return lit; }

	public void setLight(boolean value) {
		this.lit = value;
		this.setBackground(this.lit ? Color.YELLOW : Color.BLACK);
	}
	
	public void toggleState() { setLight(!this.lit); }
	
	private void toggleNeighbors() {
		for(Light n : world.getNeighbors(this)) {
			n.toggleState();
		}
	}
}


public class Lights extends JFrame implements World {
	private final int ROWS = 5, COLS = 5;
	private final Light [] cells;

	public Lights() {
		cells = new Light[ROWS * COLS];
		setLayout(new GridLayout(ROWS,COLS,0,0));
		setBackground(Color.green);
		int count = 0;
		for(int row=0; row<ROWS; row++) {
			for(int col=0; col<COLS; col++) {
				LightButton b = new LightButton(row, col, this);
				this.cells[count++] = b;
				this.add(B)/>;
			}
		}
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(500,500);
	}
	
	private Light getLight(int row, int col) {
		return (row>=0 && row<ROWS && col>=0 && col<COLS) ? this.cells[row * COLS + col] : null;
	}
	
	private class Neighbors extends java.util.ArrayList<Light> {
		public Neighbors(Light light) {
			final int row = light.getRow();
			final int col = light.getCol();
			addLight(row-1, col); addLight(row, col-1);
			addLight(row+1, col); addLight(row, col+1);
		}
		private Neighbors addLight(int row, int col) {
			Light item = getLight(row, col);
			if (item!=null) { add(item); }
			return this;
		}
	}
	
	public java.util.List<Light> getNeighbors(Light light) {
		return new Neighbors(light);
	}
}




Note the lack of static. You could still do this with just the buttons raising events, but if you really want the buttons to reach into the parent's methods, that's how I'd do it.
Was This Post Helpful? 1
  • +
  • -

#19 grimpirate  Icon User is offline

  • Pirate King
  • member icon

Reputation: 149
  • View blog
  • Posts: 714
  • Joined: 03-August 06

Re: Can one Object "Listen" to another Object's "State"

Posted 01 February 2013 - 10:11 AM

I followed your advice baavgai and took a long look at my code, plus the initial code you had posted. For whatever reason, I had decided that the ActionListener should be present within the button. I don't know why I started coding it this way, probably should've planned ahead. In any case the one thing that bothered me about your original code was that it notified all the buttons of a change. This makes sense as you originally coded it since we were initially discussing buttons communicating with one another.

However, I changed my paradigm. Jumping from your example I coded one ActionListener that is shared amongst all the buttons. This listener is now responsible for updating a GameBoard object according to the button that is pressed. The Listener only messes with the particular tiles involved in the operation as no others need to be affected. This successfully removes the coupling, static, and globals you advised against, and I do realize the benefit in this now, having done it the "hard" way as it were.

This post has been edited by grimpirate: 01 February 2013 - 10:13 AM

Was This Post Helpful? 1
  • +
  • -

#20 baavgai  Icon User is offline

  • Dreaming Coder
  • member icon

Reputation: 5874
  • View blog
  • Posts: 12,754
  • Joined: 16-October 07

Re: Can one Object "Listen" to another Object's "State"

Posted 01 February 2013 - 10:20 AM

Excellent!

Experience really is the best teacher.
Was This Post Helpful? 0
  • +
  • -

  • (2 Pages)
  • +
  • 1
  • 2