While Tudor's answer will work technically, conceptually the decision on which of the interfaces to run on (num_if) is a value that should not belong to the transaction, but to the sequence that calls it (which of course should also be randomized). Transactions should contain only a representation of the value that travels from A to B and the way in which it travels for that protocol. The specification of which A and B is normally outside of the responsibility of a transaction.
In which case, a variation on your transaction and Tudor's sequence would look like this:
class my_transaction extends uvm_sequence_item;
rand logic data;
endclass
..and..
class some_virtual_sequence extends uvm_sequence;
`uvm_declare_p_sequencer(virtual_seqr)
rand int num_if; constraint.....
task body();
my_transaction trans = my_transaction::type_id::create("my_transaction");
start_item(trans, , p_sequencer.seqrs[num_if]);
trans.randomize(); // randomization should be done after start_item()
finish_item(trans);
endtask
endclass
..running on the virtual sequencer as Tudor says:
class virtual_seqr extends uvm_sequencer;
my_sequencer seqrs[10];
endclass
The above approach also lets the randomization happen in the correct place: after start_item() returns and immediately prior to calling finish_item() which completes the sequence item.