ALW
ALW

Reputation: 11

How do I invoke blocks from subclasses in superclass method?

I think my problem is mostly syntax but it might be my overall understanding of class hierarchy. Basically it's a Deck class with an array filled with Card objects, Card is a subclass of Deck, so Deck should be able to use Card's blocks and methods, right? If it is, I am messing up the syntax horribly trying to invoke it. I am using nested while loops to fill the array, but I want each instance of the Card object to have the suit and rank of that card instead of just printing "a Card". I left where I tried to make the Card object another array of size 2 to hold the Suit and Rank, but my gst compiler says it expected an "object" so obviously I'm doing something wrong. I pasted my code so you can see what I'm talking about. Other than the block invocations it is filling up an array of size 52 with blank card objects so the rest of the Deck class is basically working.

"The Deck object class is a deck of 52 Card objects. "
Object subclass: Deck [
    | Content |
        <comment: 'I represent of Deck of Cards'>
    Deck class >> new [
        <category: 'instance creation'>
        | r |
        r := super new . 
        Transcript show: 'start ' .
        r init .
        ^r
    ] 

    init [
        <category: 'initialization'>

        |a b c|
        Content := Array new: 52 .
        a := 1 .
        c := 1 . 



        [a <= 4] whileTrue:[
            b := 1 . 
            [b <= 13] whileTrue:[
                |card|
                card := Card new .
                Card := Array new: 2 .            "This is where I'm trying to use the Card class blocks to make the Card objects have Rank and Suit" 
                Card at: 1 put: Card setRank: b| . "and here the rank"
                Card at: 2 put: Card getSuit: a| . "and the suit"
                Content at: c put: card .

                b := b + 1 .
                c := c + 1 . 

            ].

            a := a + 1 . 

        ].
     Content printNl . 
    ]

] .

"The Card object has subclass Suit and a FaceValue array of Suit and Rank. "
Object subclass: Card [
    | Suit Rank |
        <comment: 'I represent a playing Card' >
    init [
        <category: 'initialization'>
        Suit := Club .
        Rank := 1 .
        Transcript show: 'nCard ' .
        ^super init
    ]
    getSuit: suitVal [
        suitVal = 1 ifTrue: [Suit := Club] . 
        suitVal = 2 ifTrue: [Suit := Diamond] . 
        suitVal = 3 ifTrue: [Suit := Heart] . 
        suitVal = 4 ifTrue: [Suit := Spade] . 
        ^Suit 
    ] "getSuit"

    setRank: rankVal [
    Rank := rankVal . 
    ^Rank
    ]
] 

z := Deck new .

Upvotes: 1

Views: 209

Answers (2)

tukan
tukan

Reputation: 17347

Edited once - due to fede s.'s comment

Welcome to SO.

A disclaimer: I'm using Smalltalk/X-jv branch which is different smalltalk than gnu-smalltalk so I'm no gnu-smalltalk expert.

I'll point you to some deficiencies I found out. There is too much to point out. I'll give you some general ideas.

  1. I generally don't recommend using a, b, c ... z as variables. If you return to the code in some time later you won't understand it.

  2. For variables use lower case like content instead of Content. The first capital letter is reserved for global variables. In your use-case this would be a class name (don't mix it).

  3. If want to create a new instance use it like this: aCard := Card new.

  4. Don't create your whole application logic in your #init (initialize) method! You should break your code in small, readable methods.

Your init should look along these lines:

  Deck extend [
      init [
          <category: 'initialization'>
          content := Array new: 52.
          Transcript show: 'Initializing Deck...'.
      ]
  ]

Which will create an #init method with instance variable content. Don't forget to create accessors for the variable. Read the guide for gnu-smalltalk and creating instance methods.

  1. You should have comments on the whileTrue: loops. Why should anyone guess the reason for the limits?

     [a <= 4] whileTrue:[
           b := 1 . 
           [b <= 13] whileTrue:[
     ...
    
  2. Have good reason for redefining new message. A Transcript message can be ini init:

    Deck class >> new [
         <category: 'instance creation'>
         | r |
         r := self new . 
         Transcript show: 'start  '.
         r init .
         ^r
    ] 
    

Why are you redefining new? If you have an Object which is Object subclass: then it already understands new message.

At the bottom of you code you have z := Deck new. (I recommend using e.g. myDeck := Deck new.). If you want to run initialize you would simple do myDeck init.

  1. Why are you at Card >> init returning a ^super init? Maybe you wanted to do a first line super init (reading\loading superclass init) and then return ^ self? Hard to say.

  2. Suit := Club . What does that mean? Are you creating somehow a new object? (missing #new message). Are you trying to assing a string? It should be suit := 'Club'. then. (same goes for all the Sunit variable assigments).

More would be just reading from crystal ball. Try to read more about Smalltalk and I hope my tips will help you on the road.

Upvotes: 3

aka.nice
aka.nice

Reputation: 9382

A little correction: you don't invoke a block, you invoke a method.

The only blocks in the code you submitted are the arguments of whileTrue: and are private to the method (not accessible from outside).

This confusion is not your fault. It is due to the block notation used by this file format for delineating methods. Personnally, I don't like it, i find it more confusing than useful (except for the sake of looking like more mainstream languages, a tribute to file-based languages).

So how do you invoke a method? You do it by sending a message, there's no other way. All you can do is send a message. Smalltalk is message oriented. It's messages all the way down.

When receiving the message, the object will lookup for the message selector in its class method dictionary. If none, it will lookup in superclass, etc... But that's the object's own business.

So at the end, you don't really think in term of invoking a method, because a different object could respond to the same message with a different method. You must think in term of delegating the task to specialized object, that is in term of message, and associated contract.

So what are the contracts? You want a card to have a suit (Club, Spade, ...) and a rank (1 to 13). So you created a Card class with those two instances variables. So far, so good.

Then you want to create your 52 cards to fill the deck. That is instantiate the Card class. How do you do that? You have sent the message new to the Card class. You could create a more appropriate class message that will act as a Card constructor, given a suit and a rank.

That would be something like

Card class >> newWithSuit: aSuit rank: anInteger
    [<category: 'instance creation'>
    ^self basicNew setSuit: aSuit rank: anInteger ]

setSuit: aSuit rank: anInteger
    [<category: 'private'>
    suit := aSuit.
    rank := anInteger ]

Then you don't need to define individual setters (getSuit:and setRank: in your code), because you probably don't want to change those attributes afterwards, except at contruction time. That's why i generally prefer a single setter that i categorize as 'private'. That's only a convention, but that's how we define the contracts in Smalltalk, thru a set of informal conventions, and thru methods and class comments (and SUnit TestCase, especially if you embrace a test driven design).

You have decided to encode the rank in an Integer from 1 to 13. That's OK. But you also have to decide how to represent the suit. It's unclear in your code, because there's a mixture of Integer (1 to 4) and undeclared variables (Club, Spade, ...). Are these global variables?

Then you don't have to encode those suit and rank in an Array of size 2, that's absolutely un-necessary since all these data are already contained in the Card objects. So you would fill the deck with:

content at: deckRank put: (Card newWithSuit: suitNumber rank: rankNumber).

Mind the parentheses: you send message at:put: to content with two parameters, deckRank and the the value returned by another message send (Card newWithSuit: suitNumber rank: rankNumber), which we expect to be a properly initialized instance of Card.

In the code you provided Card at: 1 put: Card setRank: b you send a message at:put:setRank: to the class Card with 3 parameters, the integer literal 1, the class Card and the object pointed by variable b. That's probably not your intention. There also a bar | following b which does not quite look like a correct syntax to me. Bars are for delineating temporary variables, or separating block arguments from block instructions inside a block, or can also be a binary message, but in that case require an argument (every binary message like + - * / have a receiver and an argument).

I hope I gave you some useful hints, but maybe you have to read and apply some step-by-step Smalltalk tutorials to become better acquainted with those basic notions.

Upvotes: 2

Related Questions