1

I've recently came across a case where it would be very convenient to convert an instance of a class to a subclass, while the instance has been created within the parent class. But I've never seen such thing. So is there a way to do something like:

class Foo {
  var name: String
}

class Bar: Foo {
  var friendName: String
}

let foo = Foo(name: "Alice")
foo.toBar(friendName: "Bob")
// foo now of type Bar, as if I'd done
// foo = Bar(name: "Alice", friendName: "Bob")

If that's not possible, is there some reasons this would be impossible from a design perspective?

===edit=== description of a use case where it could make sense

Let say there's two views representing what correspond to the same database record for a book, on is a just a preview of the book and another is a more complex view. Models could be:

protocol BookMetaDelegate {
  func onReadStatusUpdate()
}

/// describe a book
class BookMeta {
  var delegate: BookMetaDelegate?
  private var _hasBeenRead: Bool
  var hasBeenRead: Bool {
    get {
      return _hasBeenRead
    }
    set {
      guard newValue != _hasBeenRead else { return }
      _hasBeenRead = newValue
      delegate?.onReadStatusUpdate()
    }
  }
  var title: String
}

/// contains all the content of a book
class Book: BookMeta {
  var content: BookContent
  var lastPageRead: Int

  /// some logic that only makes sense in a Book instance
  func getLastPageRead() {
    return content.getPage(lastPageRead)
  }
}

and views could look like:

class BookPreview: UIView, BookMetaDelegate {
  var book: BookMeta
  init(book: BookMeta) {
    book.delegate = self
  }
  func onReadStatusUpdate() {
    print("read status has changed! UI should update")
  }
}

class BookView: UIView {
  var book: Book
  init(book: Book) {
    book.hasBeenRead = true
  }
}

Then things could happen like

fetch(bookMetaWithId: 123).then { bookMeta in // bookMeta is of type BookMeta
  let preview = BookPreview(book: bookMeta)
  ...

  fetch(contentOf: bookMeta).then { content, lastPageRead in
    bookMeta.asBook(content: content, lastPageRead: lastPageRead)
    let bookView = BookView(book: bookMeta) // doing so will change the hasBeenRead flag and message the instance's delegate, ie the preview
    ...
  }
}
Guig
  • 9,891
  • 7
  • 64
  • 126
  • No, it's not possible. It would be possible to create a *new* `Bar` from an existing `Foo`, but you can't change the type of an existing object, and there would be no benefit to doing so. – Crowman Sep 04 '16 at 02:18
  • Why would there be no benefit to doing so? That more of a point of view. Let's say I've two classes that represent a user's profile and a user with a lot of other informations. I first load the profile, and later I fetch the other info. I might then want to downcast my UserProfile instance using the extra data – Guig Sep 04 '16 at 02:38
  • 1
    You're using "downcast" wrong - you downcast to an object's actual type, not to a different one. In your example, there is no advantage over creating a new object, or to making the "other info" a (possibly) optional property in your original class. That's why there's no benefit. Having "two classes that represent a user's profile and a user with a lot of other informations" seems to be the actual problem. Creating a better design is much more sensible than completely breaking the type system. – Crowman Sep 04 '16 at 02:43
  • Advantage over creating a new object would be that if some element A has a reference to the instance, and that let's say some other element B that needs a downcasted version of the instance updates the downcasted/copied instance in a way that A is interested, it's then very annoying to let A know about it if the instance is duplicates, ie A and B have references to distinct instances. – Guig Sep 04 '16 at 02:48
  • Concerning using optionals over a subclass, I've found that sometime it's much clearer to have distinct logic according to what data is available and that class distinction is great to do that. In the small example, sure `friendName` could be an optional. – Guig Sep 04 '16 at 02:50
  • On the contrary, the fact that you can have two references to the same object is as good a reason as any that you'll find *against* what you propose. There'd be total chaos if you did this. You could never use a reference to an object without knowing if its type had unexpectedly changed since the last time you used it. – Crowman Sep 04 '16 at 02:50
  • "I've found that sometime it's much clearer..." - the fact that the solution you propose seems "convenient" for your situation is fairly convincing evidence that it's actually not clearer at all. If something that would break the language seems like a convenient solution, then it's a fairly safe bet the actual problem is with what you are trying to do in the first place. – Crowman Sep 04 '16 at 02:53
  • I take your point that I should not need something that would break a language. I've edited the question to describe an example where I believe such thing would be useful, if it was possible, which is not the case. I know there are things like redux etc to simplify the messages passing. and I'm thinking of how to have a more instance centered approach over a general store one. – Guig Sep 04 '16 at 03:21
  • I think your example proves my point. You create an inheritance hierarchy where you assert that a book "is a" book description - which it clearly isn't - and all your other problems begin from there. The problem you are seeking to solve is caused by your initial bad design choice - with a more coherent design in the first place, there would be no problem in need of a solution, here. – Crowman Sep 04 '16 at 03:29
  • Yeah the book was probably not the best example... Sorry. If I replace book by user and book description by user preview, then I think it makes some sense to say that a user also knows all there is to know about a user preview; and that additionally since they could both be represented by the same record in the database, it makes sense to have a single instance for the shared data on the client. This could also be done with having the user class hold a reference to the preview which I guess it the best way to go since you can't down cast anyway. – Guig Sep 04 '16 at 03:43

1 Answers1

0

Thinking more about it, it sounds like that if such thing was possible, it'd break things like:

class Foo {
  var name: String
}

class Bar: Foo {
  var friendName: String
}

class Bla: Foo {
  var surname: String
}

func something(foo: Foo) {
  foo.toBla(surname: "Will")
}

let bar = Bar(name: "Alice", friendName: "Bob")
something(foo: bar) // what does that do ??? is bar a Bla now ?

so that'd be a good reason for making such casting impossible.

Guig
  • 9,891
  • 7
  • 64
  • 126